mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add inactivity cleanup and failure cleanup configuration fields to Template Schedule Form (#7402)
* added workspace actions entitlement * added workspace actions experiment * added new route for template enterprise meta * removing new route; repurposing old * add new fields to get endpoints * removed workspace actions experiment * added logic to enterprise template store * added new form fields * feature flagged new fields * fix validation * fixed submit btn * fix tests * changed ttl defaults * added FE tests * added BE tests * fixed lint * adjusted comment language * fixing unstaged changes check * fix test * Update coderd/database/migrations/000122_add_template_cleanup_ttls.down.sql Co-authored-by: Dean Sheather <dean@deansheather.com> * Update coderd/database/migrations/000122_add_template_cleanup_ttls.up.sql Co-authored-by: Dean Sheather <dean@deansheather.com> --------- Co-authored-by: Dean Sheather <dean@deansheather.com>
This commit is contained in:
co-authored by
Dean Sheather
parent
3632ac8c01
commit
5ffa6dae50
@@ -194,6 +194,8 @@ export interface CreateTemplateRequest {
|
||||
readonly allow_user_cancel_workspace_jobs?: boolean
|
||||
readonly allow_user_autostart?: boolean
|
||||
readonly allow_user_autostop?: boolean
|
||||
readonly failure_ttl_ms?: number
|
||||
readonly inactivity_ttl_ms?: number
|
||||
}
|
||||
|
||||
// From codersdk/templateversions.go
|
||||
@@ -841,6 +843,8 @@ export interface Template {
|
||||
readonly allow_user_autostart: boolean
|
||||
readonly allow_user_autostop: boolean
|
||||
readonly allow_user_cancel_workspace_jobs: boolean
|
||||
readonly failure_ttl_ms: number
|
||||
readonly inactivity_ttl_ms: number
|
||||
}
|
||||
|
||||
// From codersdk/templates.go
|
||||
@@ -1010,6 +1014,8 @@ export interface UpdateTemplateMeta {
|
||||
readonly allow_user_autostart?: boolean
|
||||
readonly allow_user_autostop?: boolean
|
||||
readonly allow_user_cancel_workspace_jobs?: boolean
|
||||
readonly failure_ttl_ms?: number
|
||||
readonly inactivity_ttl_ms?: number
|
||||
}
|
||||
|
||||
// From codersdk/users.go
|
||||
@@ -1376,7 +1382,6 @@ export type FeatureName =
|
||||
| "scim"
|
||||
| "template_rbac"
|
||||
| "user_limit"
|
||||
| "workspace_actions"
|
||||
| "workspace_proxy"
|
||||
export const FeatureNames: FeatureName[] = [
|
||||
"advanced_template_scheduling",
|
||||
@@ -1389,7 +1394,6 @@ export const FeatureNames: FeatureName[] = [
|
||||
"scim",
|
||||
"template_rbac",
|
||||
"user_limit",
|
||||
"workspace_actions",
|
||||
"workspace_proxy",
|
||||
]
|
||||
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
"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.",
|
||||
"failureTTLHelperText_zero": "Coder will not automatically stop failed workspaces",
|
||||
"failureTTLHelperText_one": "Coder will automatically stop failed workspaces after {{count}} day.",
|
||||
"failureTTLHelperText_other": "Coder will automatically stop failed workspaces after {{count}} days.",
|
||||
"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.",
|
||||
"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.",
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
import TextField from "@material-ui/core/TextField"
|
||||
import { Template, UpdateTemplateMeta } from "api/typesGenerated"
|
||||
import { FormikTouched, useFormik } from "formik"
|
||||
import { FC } from "react"
|
||||
import { FC, ChangeEvent } from "react"
|
||||
import { getFormHelpers } from "utils/formUtils"
|
||||
import * as Yup from "yup"
|
||||
import i18next from "i18next"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Maybe } from "components/Conditionals/Maybe"
|
||||
import { FormSection, HorizontalForm, FormFooter } from "components/Form/Form"
|
||||
import {
|
||||
FormSection,
|
||||
HorizontalForm,
|
||||
FormFooter,
|
||||
FormFields,
|
||||
} 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"
|
||||
import FormControlLabel from "@material-ui/core/FormControlLabel"
|
||||
import Switch from "@material-ui/core/Switch"
|
||||
|
||||
const TTLHelperText = ({
|
||||
ttl,
|
||||
@@ -32,6 +39,14 @@ const TTLHelperText = ({
|
||||
|
||||
const MAX_TTL_DAYS = 7
|
||||
const MS_HOUR_CONVERSION = 3600000
|
||||
const MS_DAY_CONVERSION = 86400000
|
||||
const FAILURE_CLEANUP_DEFAULT = 7
|
||||
const INACTIVITY_CLEANUP_DEFAULT = 180
|
||||
|
||||
export interface TemplateScheduleFormValues extends UpdateTemplateMeta {
|
||||
failure_cleanup_enabled: boolean
|
||||
inactivity_cleanup_enabled: boolean
|
||||
}
|
||||
|
||||
export const getValidationSchema = (): Yup.AnyObjectSchema =>
|
||||
Yup.object({
|
||||
@@ -49,6 +64,36 @@ export const getValidationSchema = (): Yup.AnyObjectSchema =>
|
||||
24 * MAX_TTL_DAYS /* 7 days in hours */,
|
||||
i18next.t("maxTTLMaxError", { ns: "templateSettingsPage" }),
|
||||
),
|
||||
failure_ttl_ms: Yup.number()
|
||||
.integer()
|
||||
.min(0, "Failure cleanup days must not be less than 0.")
|
||||
.test(
|
||||
"positive-if-enabled",
|
||||
"Failure cleanup days must be greater than zero when enabled.",
|
||||
function (value) {
|
||||
const parent = this.parent as TemplateScheduleFormValues
|
||||
if (parent.failure_cleanup_enabled) {
|
||||
return Boolean(value)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
},
|
||||
),
|
||||
inactivity_ttl_ms: Yup.number()
|
||||
.integer()
|
||||
.min(0, "Inactivity cleanup days must not be less than 0.")
|
||||
.test(
|
||||
"positive-if-enabled",
|
||||
"Inactivity cleanup days must be greater than zero when enabled.",
|
||||
function (value) {
|
||||
const parent = this.parent as TemplateScheduleFormValues
|
||||
if (parent.inactivity_cleanup_enabled) {
|
||||
return Boolean(value)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
},
|
||||
),
|
||||
allow_user_autostart: Yup.boolean(),
|
||||
allow_user_autostop: Yup.boolean(),
|
||||
})
|
||||
@@ -60,6 +105,7 @@ export interface TemplateScheduleForm {
|
||||
isSubmitting: boolean
|
||||
error?: unknown
|
||||
allowAdvancedScheduling: boolean
|
||||
allowWorkspaceActions: boolean
|
||||
// Helpful to show field errors on Storybook
|
||||
initialTouched?: FormikTouched<UpdateTemplateMeta>
|
||||
}
|
||||
@@ -70,22 +116,34 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
||||
onCancel,
|
||||
error,
|
||||
allowAdvancedScheduling,
|
||||
allowWorkspaceActions,
|
||||
isSubmitting,
|
||||
initialTouched,
|
||||
}) => {
|
||||
const { t: commonT } = useTranslation("common")
|
||||
const validationSchema = getValidationSchema()
|
||||
const form = useFormik<UpdateTemplateMeta>({
|
||||
const form = useFormik<TemplateScheduleFormValues>({
|
||||
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
|
||||
// the API ignores these values, 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,
|
||||
failure_ttl_ms: allowAdvancedScheduling
|
||||
? template.failure_ttl_ms / MS_DAY_CONVERSION
|
||||
: 0,
|
||||
inactivity_ttl_ms: allowAdvancedScheduling
|
||||
? template.inactivity_ttl_ms / MS_DAY_CONVERSION
|
||||
: 0,
|
||||
|
||||
allow_user_autostart: template.allow_user_autostart,
|
||||
allow_user_autostop: template.allow_user_autostop,
|
||||
failure_cleanup_enabled:
|
||||
allowAdvancedScheduling && Boolean(template.failure_ttl_ms),
|
||||
inactivity_cleanup_enabled:
|
||||
allowAdvancedScheduling && Boolean(template.inactivity_ttl_ms),
|
||||
},
|
||||
validationSchema,
|
||||
onSubmit: (formData) => {
|
||||
@@ -97,16 +155,64 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
||||
max_ttl_ms: formData.max_ttl_ms
|
||||
? formData.max_ttl_ms * MS_HOUR_CONVERSION
|
||||
: undefined,
|
||||
failure_ttl_ms: formData.failure_ttl_ms
|
||||
? formData.failure_ttl_ms * MS_DAY_CONVERSION
|
||||
: undefined,
|
||||
inactivity_ttl_ms: formData.inactivity_ttl_ms
|
||||
? formData.inactivity_ttl_ms * MS_DAY_CONVERSION
|
||||
: undefined,
|
||||
|
||||
allow_user_autostart: formData.allow_user_autostart,
|
||||
allow_user_autostop: formData.allow_user_autostop,
|
||||
})
|
||||
},
|
||||
initialTouched,
|
||||
})
|
||||
const getFieldHelpers = getFormHelpers<UpdateTemplateMeta>(form, error)
|
||||
const getFieldHelpers = getFormHelpers<TemplateScheduleFormValues>(
|
||||
form,
|
||||
error,
|
||||
)
|
||||
const { t } = useTranslation("templateSettingsPage")
|
||||
const styles = useStyles()
|
||||
|
||||
const handleToggleFailureCleanup = async (e: ChangeEvent) => {
|
||||
form.handleChange(e)
|
||||
if (!form.values.failure_cleanup_enabled) {
|
||||
// fill failure_ttl_ms with defaults
|
||||
await form.setValues({
|
||||
...form.values,
|
||||
failure_cleanup_enabled: true,
|
||||
failure_ttl_ms: FAILURE_CLEANUP_DEFAULT,
|
||||
})
|
||||
} else {
|
||||
// clear failure_ttl_ms
|
||||
await form.setValues({
|
||||
...form.values,
|
||||
failure_cleanup_enabled: false,
|
||||
failure_ttl_ms: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggleInactivityCleanup = async (e: ChangeEvent) => {
|
||||
form.handleChange(e)
|
||||
if (!form.values.inactivity_cleanup_enabled) {
|
||||
// fill inactivity_ttl_ms with defaults
|
||||
await form.setValues({
|
||||
...form.values,
|
||||
inactivity_cleanup_enabled: true,
|
||||
inactivity_ttl_ms: INACTIVITY_CLEANUP_DEFAULT,
|
||||
})
|
||||
} else {
|
||||
// clear inactivity_ttl_ms
|
||||
await form.setValues({
|
||||
...form.values,
|
||||
inactivity_cleanup_enabled: false,
|
||||
inactivity_ttl_ms: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<HorizontalForm
|
||||
onSubmit={form.handleSubmit}
|
||||
@@ -215,8 +321,85 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
||||
</Stack>
|
||||
</Stack>
|
||||
</FormSection>
|
||||
|
||||
<FormFooter onCancel={onCancel} isLoading={isSubmitting} />
|
||||
{allowAdvancedScheduling && allowWorkspaceActions && (
|
||||
<>
|
||||
<FormSection
|
||||
title="Failure Cleanup"
|
||||
description="When enabled, Coder will automatically stop workspaces that are in a failed state after a specified number of days."
|
||||
>
|
||||
<FormFields>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
name="failureCleanupEnabled"
|
||||
checked={form.values.failure_cleanup_enabled}
|
||||
onChange={handleToggleFailureCleanup}
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Enable Failure Cleanup"
|
||||
/>
|
||||
<TextField
|
||||
{...getFieldHelpers(
|
||||
"failure_ttl_ms",
|
||||
<TTLHelperText
|
||||
translationName="failureTTLHelperText"
|
||||
ttl={form.values.failure_ttl_ms}
|
||||
/>,
|
||||
)}
|
||||
disabled={isSubmitting || !form.values.failure_cleanup_enabled}
|
||||
fullWidth
|
||||
inputProps={{ min: 0, step: 1 }}
|
||||
label="Time until cleanup (days)"
|
||||
variant="outlined"
|
||||
type="number"
|
||||
aria-label="Failure Cleanup"
|
||||
/>
|
||||
</FormFields>
|
||||
</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."
|
||||
>
|
||||
<FormFields>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
name="inactivityCleanupEnabled"
|
||||
checked={form.values.inactivity_cleanup_enabled}
|
||||
onChange={handleToggleInactivityCleanup}
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Enable Inactivity Cleanup"
|
||||
/>
|
||||
<TextField
|
||||
{...getFieldHelpers(
|
||||
"inactivity_ttl_ms",
|
||||
<TTLHelperText
|
||||
translationName="inactivityTTLHelperText"
|
||||
ttl={form.values.inactivity_ttl_ms}
|
||||
/>,
|
||||
)}
|
||||
disabled={
|
||||
isSubmitting || !form.values.inactivity_cleanup_enabled
|
||||
}
|
||||
fullWidth
|
||||
inputProps={{ min: 0, step: 1 }}
|
||||
label="Time until cleanup (days)"
|
||||
variant="outlined"
|
||||
type="number"
|
||||
aria-label="Inactivity Cleanup"
|
||||
/>
|
||||
</FormFields>
|
||||
</FormSection>
|
||||
</>
|
||||
)}
|
||||
<FormFooter
|
||||
onCancel={onCancel}
|
||||
isLoading={isSubmitting}
|
||||
submitDisabled={!form.isValid || !form.dirty}
|
||||
/>
|
||||
</HorizontalForm>
|
||||
)
|
||||
}
|
||||
|
||||
+104
-6
@@ -6,11 +6,11 @@ import { Language as FooterFormLanguage } from "components/FormFooter/FormFooter
|
||||
import {
|
||||
MockEntitlementsWithScheduling,
|
||||
MockTemplate,
|
||||
} from "../../../testHelpers/entities"
|
||||
} from "testHelpers/entities"
|
||||
import {
|
||||
renderWithTemplateSettingsLayout,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "../../../testHelpers/renderHelpers"
|
||||
} from "testHelpers/renderHelpers"
|
||||
import { getValidationSchema } from "./TemplateScheduleForm"
|
||||
import TemplateSchedulePage from "./TemplateSchedulePage"
|
||||
import i18next from "i18next"
|
||||
@@ -20,6 +20,8 @@ const { t } = i18next
|
||||
const validFormValues = {
|
||||
default_ttl_ms: 1,
|
||||
max_ttl_ms: 2,
|
||||
failure_ttl_ms: 7,
|
||||
inactivity_ttl_ms: 180,
|
||||
}
|
||||
|
||||
const renderTemplateSchedulePage = async () => {
|
||||
@@ -33,9 +35,13 @@ const renderTemplateSchedulePage = async () => {
|
||||
const fillAndSubmitForm = async ({
|
||||
default_ttl_ms,
|
||||
max_ttl_ms,
|
||||
failure_ttl_ms,
|
||||
inactivity_ttl_ms,
|
||||
}: {
|
||||
default_ttl_ms: number
|
||||
max_ttl_ms: number
|
||||
failure_ttl_ms: number
|
||||
inactivity_ttl_ms: number
|
||||
}) => {
|
||||
const user = userEvent.setup()
|
||||
const defaultTtlLabel = t("defaultTtlLabel", { ns: "templateSettingsPage" })
|
||||
@@ -48,6 +54,16 @@ const fillAndSubmitForm = async ({
|
||||
await user.clear(maxTtlField)
|
||||
await user.type(maxTtlField, max_ttl_ms.toString())
|
||||
|
||||
const failureTtlField = screen.getByRole("checkbox", {
|
||||
name: /Failure Cleanup/i,
|
||||
})
|
||||
await user.type(failureTtlField, failure_ttl_ms.toString())
|
||||
|
||||
const inactivityTtlField = screen.getByRole("checkbox", {
|
||||
name: /Inactivity Cleanup/i,
|
||||
})
|
||||
await user.type(inactivityTtlField, inactivity_ttl_ms.toString())
|
||||
|
||||
const submitButton = await screen.findByText(
|
||||
FooterFormLanguage.defaultSubmitLabel,
|
||||
)
|
||||
@@ -59,6 +75,9 @@ describe("TemplateSchedulePage", () => {
|
||||
jest
|
||||
.spyOn(API, "getEntitlements")
|
||||
.mockResolvedValue(MockEntitlementsWithScheduling)
|
||||
|
||||
// remove when https://github.com/coder/coder/milestone/19 is completed.
|
||||
jest.spyOn(API, "getExperiments").mockResolvedValue(["workspace_actions"])
|
||||
})
|
||||
|
||||
it("succeeds", async () => {
|
||||
@@ -71,7 +90,7 @@ describe("TemplateSchedulePage", () => {
|
||||
await waitFor(() => expect(API.updateTemplateMeta).toBeCalledTimes(1))
|
||||
})
|
||||
|
||||
test("ttl is converted to and from hours", async () => {
|
||||
test("default and max ttl is converted to and from hours", async () => {
|
||||
await renderTemplateSchedulePage()
|
||||
|
||||
jest.spyOn(API, "updateTemplateMeta").mockResolvedValueOnce({
|
||||
@@ -92,7 +111,28 @@ describe("TemplateSchedulePage", () => {
|
||||
)
|
||||
})
|
||||
|
||||
it("allows a ttl of 7 days", () => {
|
||||
test("failure and inactivity ttl converted to and from days", async () => {
|
||||
await renderTemplateSchedulePage()
|
||||
|
||||
jest.spyOn(API, "updateTemplateMeta").mockResolvedValueOnce({
|
||||
...MockTemplate,
|
||||
...validFormValues,
|
||||
})
|
||||
|
||||
await fillAndSubmitForm(validFormValues)
|
||||
await waitFor(() => expect(API.updateTemplateMeta).toBeCalledTimes(1))
|
||||
await waitFor(() =>
|
||||
expect(API.updateTemplateMeta).toBeCalledWith(
|
||||
"test-template",
|
||||
expect.objectContaining({
|
||||
failure_ttl_ms: validFormValues.failure_ttl_ms * 86400000,
|
||||
inactivity_ttl_ms: validFormValues.inactivity_ttl_ms * 86400000,
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
it("allows a default ttl of 7 days", () => {
|
||||
const values: UpdateTemplateMeta = {
|
||||
...validFormValues,
|
||||
default_ttl_ms: 24 * 7,
|
||||
@@ -101,7 +141,7 @@ describe("TemplateSchedulePage", () => {
|
||||
expect(validate).not.toThrowError()
|
||||
})
|
||||
|
||||
it("allows ttl of 0", () => {
|
||||
it("allows default ttl of 0", () => {
|
||||
const values: UpdateTemplateMeta = {
|
||||
...validFormValues,
|
||||
default_ttl_ms: 0,
|
||||
@@ -110,7 +150,7 @@ describe("TemplateSchedulePage", () => {
|
||||
expect(validate).not.toThrowError()
|
||||
})
|
||||
|
||||
it("disallows a ttl of 7 days + 1 hour", () => {
|
||||
it("disallows a default ttl of 7 days + 1 hour", () => {
|
||||
const values: UpdateTemplateMeta = {
|
||||
...validFormValues,
|
||||
default_ttl_ms: 24 * 7 + 1,
|
||||
@@ -120,4 +160,62 @@ describe("TemplateSchedulePage", () => {
|
||||
t("defaultTTLMaxError", { ns: "templateSettingsPage" }),
|
||||
)
|
||||
})
|
||||
|
||||
it("allows a failure ttl of 7 days", () => {
|
||||
const values: UpdateTemplateMeta = {
|
||||
...validFormValues,
|
||||
failure_ttl_ms: 86400000 * 7,
|
||||
}
|
||||
const validate = () => getValidationSchema().validateSync(values)
|
||||
expect(validate).not.toThrowError()
|
||||
})
|
||||
|
||||
it("allows failure ttl of 0", () => {
|
||||
const values: UpdateTemplateMeta = {
|
||||
...validFormValues,
|
||||
failure_ttl_ms: 0,
|
||||
}
|
||||
const validate = () => getValidationSchema().validateSync(values)
|
||||
expect(validate).not.toThrowError()
|
||||
})
|
||||
|
||||
it("disallows a negative failure ttl", () => {
|
||||
const values: UpdateTemplateMeta = {
|
||||
...validFormValues,
|
||||
failure_ttl_ms: -1,
|
||||
}
|
||||
const validate = () => getValidationSchema().validateSync(values)
|
||||
expect(validate).toThrowError(
|
||||
"Failure cleanup days must not be less than 0.",
|
||||
)
|
||||
})
|
||||
|
||||
it("allows an inactivity ttl of 7 days", () => {
|
||||
const values: UpdateTemplateMeta = {
|
||||
...validFormValues,
|
||||
inactivity_ttl_ms: 86400000 * 7,
|
||||
}
|
||||
const validate = () => getValidationSchema().validateSync(values)
|
||||
expect(validate).not.toThrowError()
|
||||
})
|
||||
|
||||
it("allows an inactivity ttl of 0", () => {
|
||||
const values: UpdateTemplateMeta = {
|
||||
...validFormValues,
|
||||
inactivity_ttl_ms: 0,
|
||||
}
|
||||
const validate = () => getValidationSchema().validateSync(values)
|
||||
expect(validate).not.toThrowError()
|
||||
})
|
||||
|
||||
it("disallows a negative inactivity ttl", () => {
|
||||
const values: UpdateTemplateMeta = {
|
||||
...validFormValues,
|
||||
inactivity_ttl_ms: -1,
|
||||
}
|
||||
const validate = () => getValidationSchema().validateSync(values)
|
||||
expect(validate).toThrowError(
|
||||
"Inactivity cleanup days must not be less than 0.",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -14,9 +14,13 @@ const TemplateSchedulePage: FC = () => {
|
||||
const { template: templateName } = useParams() as { template: string }
|
||||
const navigate = useNavigate()
|
||||
const { template } = useTemplateSettingsContext()
|
||||
const { entitlements } = useDashboard()
|
||||
const { entitlements, experiments } = useDashboard()
|
||||
const allowAdvancedScheduling =
|
||||
entitlements.features["advanced_template_scheduling"].enabled
|
||||
// This check can be removed when https://github.com/coder/coder/milestone/19
|
||||
// is merged up
|
||||
const allowWorkspaceActions = experiments.includes("workspace_actions")
|
||||
|
||||
const {
|
||||
mutate: updateTemplate,
|
||||
isLoading: isSubmitting,
|
||||
@@ -37,6 +41,7 @@ const TemplateSchedulePage: FC = () => {
|
||||
</Helmet>
|
||||
<TemplateSchedulePageView
|
||||
allowAdvancedScheduling={allowAdvancedScheduling}
|
||||
allowWorkspaceActions={allowWorkspaceActions}
|
||||
isSubmitting={isSubmitting}
|
||||
template={template}
|
||||
submitError={submitError}
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ export default {
|
||||
component: TemplateSchedulePageView,
|
||||
args: {
|
||||
allowAdvancedScheduling: true,
|
||||
allowWorkspaceActions: true,
|
||||
template: MockTemplate,
|
||||
onSubmit: action("onSubmit"),
|
||||
onCancel: action("cancel"),
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface TemplateSchedulePageViewProps {
|
||||
submitError?: unknown
|
||||
initialTouched?: ComponentProps<typeof TemplateScheduleForm>["initialTouched"]
|
||||
allowAdvancedScheduling: boolean
|
||||
allowWorkspaceActions: boolean
|
||||
}
|
||||
|
||||
export const TemplateSchedulePageView: FC<TemplateSchedulePageViewProps> = ({
|
||||
@@ -20,6 +21,7 @@ export const TemplateSchedulePageView: FC<TemplateSchedulePageViewProps> = ({
|
||||
onSubmit,
|
||||
isSubmitting,
|
||||
allowAdvancedScheduling,
|
||||
allowWorkspaceActions,
|
||||
submitError,
|
||||
initialTouched,
|
||||
}) => {
|
||||
@@ -33,6 +35,7 @@ export const TemplateSchedulePageView: FC<TemplateSchedulePageViewProps> = ({
|
||||
|
||||
<TemplateScheduleForm
|
||||
allowAdvancedScheduling={allowAdvancedScheduling}
|
||||
allowWorkspaceActions={allowWorkspaceActions}
|
||||
initialTouched={initialTouched}
|
||||
isSubmitting={isSubmitting}
|
||||
template={template}
|
||||
|
||||
@@ -341,6 +341,8 @@ export const MockTemplate: TypesGen.Template = {
|
||||
created_by_name: "test_creator",
|
||||
icon: "/icon/code.svg",
|
||||
allow_user_cancel_workspace_jobs: true,
|
||||
failure_ttl_ms: 0,
|
||||
inactivity_ttl_ms: 0,
|
||||
allow_user_autostart: false,
|
||||
allow_user_autostop: false,
|
||||
}
|
||||
@@ -1340,7 +1342,7 @@ export const MockEntitlementsWithScheduling: TypesGen.Entitlements = {
|
||||
}),
|
||||
}
|
||||
|
||||
export const MockExperiments: TypesGen.Experiment[] = []
|
||||
export const MockExperiments: TypesGen.Experiment[] = ["workspace_actions"]
|
||||
|
||||
export const MockAuditLog: TypesGen.AuditLog = {
|
||||
id: "fbd2116a-8961-4954-87ae-e4575bd29ce0",
|
||||
|
||||
Reference in New Issue
Block a user