chore: rename locked to dormant (#9290)

* chore: rename locked to dormant

- The following columns have been updated:
  - workspace.locked_at -> dormant_at
  - template.inactivity_ttl -> time_til_dormant
  - template.locked_ttl -> time_til_dormant_autodelete

This change has also been reflected in the SDK.

A route has also been updated from /workspaces/<id>/lock to /workspaces/<id>/dormant
This commit is contained in:
Jon Ayers
2023-08-24 13:25:54 -05:00
committed by GitHub
parent ebd878b6b5
commit 7f14b50dbe
67 changed files with 1174 additions and 1206 deletions
+5 -5
View File
@@ -554,16 +554,16 @@ export const cancelWorkspaceBuild = async (
return response.data
}
export const updateWorkspaceLock = async (
export const updateWorkspaceDormancy = async (
workspaceId: string,
lock: boolean,
dormant: boolean,
): Promise<TypesGen.Workspace> => {
const data: TypesGen.UpdateWorkspaceLock = {
lock: lock,
const data: TypesGen.UpdateWorkspaceDormancy = {
dormant: dormant,
}
const response = await axios.put(
`/api/v2/workspaces/${workspaceId}/lock`,
`/api/v2/workspaces/${workspaceId}/dormant`,
data,
)
return response.data
+10 -10
View File
@@ -200,8 +200,8 @@ export interface CreateTemplateRequest {
readonly allow_user_autostart?: boolean
readonly allow_user_autostop?: boolean
readonly failure_ttl_ms?: number
readonly inactivity_ttl_ms?: number
readonly locked_ttl_ms?: number
readonly dormant_ttl_ms?: number
readonly delete_ttl_ms?: number
readonly disable_everyone_group_access: boolean
}
@@ -917,8 +917,8 @@ export interface Template {
readonly allow_user_autostop: boolean
readonly allow_user_cancel_workspace_jobs: boolean
readonly failure_ttl_ms: number
readonly inactivity_ttl_ms: number
readonly locked_ttl_ms: number
readonly time_til_dormant_ms: number
readonly time_til_dormant_autodelete_ms: number
}
// From codersdk/templates.go
@@ -1152,10 +1152,10 @@ export interface UpdateTemplateMeta {
readonly allow_user_autostop?: boolean
readonly allow_user_cancel_workspace_jobs?: boolean
readonly failure_ttl_ms?: number
readonly inactivity_ttl_ms?: number
readonly locked_ttl_ms?: number
readonly time_til_dormant_ms?: number
readonly time_til_dormant_autodelete_ms?: number
readonly update_workspace_last_used_at: boolean
readonly update_workspace_locked_at: boolean
readonly update_workspace_dormant_at: boolean
}
// From codersdk/users.go
@@ -1180,8 +1180,8 @@ export interface UpdateWorkspaceAutostartRequest {
}
// From codersdk/workspaces.go
export interface UpdateWorkspaceLock {
readonly lock: boolean
export interface UpdateWorkspaceDormancy {
readonly dormant: boolean
}
// From codersdk/workspaceproxy.go
@@ -1310,7 +1310,7 @@ export interface Workspace {
readonly ttl_ms?: number
readonly last_used_at: string
readonly deleting_at?: string
readonly locked_at?: string
readonly dormant_at?: string
readonly health: WorkspaceHealth
}
@@ -160,7 +160,7 @@ export interface ScheduleDialogProps extends ConfirmDialogProps {
readonly inactiveWorkspacesToGoDormantInWeek: number
readonly dormantWorkspacesToBeDeleted: number
readonly dormantWorkspacesToBeDeletedInWeek: number
readonly updateLockedWorkspaces: (confirm: boolean) => void
readonly updateDormantWorkspaces: (confirm: boolean) => void
readonly updateInactiveWorkspaces: (confirm: boolean) => void
readonly dormantValueChanged: boolean
readonly deletionValueChanged: boolean
@@ -180,7 +180,7 @@ export const ScheduleDialog: FC<PropsWithChildren<ScheduleDialogProps>> = ({
inactiveWorkspacesToGoDormantInWeek,
dormantWorkspacesToBeDeleted,
dormantWorkspacesToBeDeletedInWeek,
updateLockedWorkspaces,
updateDormantWorkspaces,
updateInactiveWorkspaces,
dormantValueChanged,
deletionValueChanged,
@@ -250,7 +250,7 @@ export const ScheduleDialog: FC<PropsWithChildren<ScheduleDialogProps>> = ({
<Checkbox
size="small"
onChange={(e) => {
updateLockedWorkspaces(e.target.checked)
updateDormantWorkspaces(e.target.checked)
}}
/>
}
@@ -34,7 +34,7 @@ export const actionsByWorkspaceStatus = (
workspace: Workspace,
status: WorkspaceStatus,
): WorkspaceAbilities => {
if (workspace.locked_at) {
if (workspace.dormant_at) {
return {
actions: [ButtonTypesEnum.activate],
canCancel: false,
@@ -1,17 +0,0 @@
import { Workspace } from "api/typesGenerated"
import { useIsWorkspaceActionsEnabled } from "components/Dashboard/DashboardProvider"
import { Pill } from "components/Pill/Pill"
import LockIcon from "@mui/icons-material/Lock"
export const LockedBadge = ({
workspace,
}: {
workspace: Workspace
}): JSX.Element | null => {
const experimentEnabled = useIsWorkspaceActionsEnabled()
if (!workspace.locked_at || !experimentEnabled) {
return null
}
return <Pill icon={<LockIcon />} text="Locked" type="error" />
}
@@ -27,8 +27,8 @@ export const DormantWorkspaceBanner = ({
return null
}
const hasLockedWorkspaces = workspaces.find(
(workspace) => workspace.locked_at,
const hasDormantWorkspaces = workspaces.find(
(workspace) => workspace.dormant_at,
)
const hasDeletionScheduledWorkspaces = workspaces.find(
@@ -38,7 +38,7 @@ export const DormantWorkspaceBanner = ({
if (
// Only show this if the experiment is included.
!experimentEnabled ||
!hasLockedWorkspaces ||
!hasDormantWorkspaces ||
// Banners should be redisplayed after dismissal when additional workspaces are newly scheduled for deletion
!shouldRedisplayBanner
) {
@@ -59,16 +59,16 @@ export const DormantWorkspaceBanner = ({
if (
hasDeletionScheduledWorkspaces &&
hasDeletionScheduledWorkspaces.deleting_at &&
hasDeletionScheduledWorkspaces.locked_at
hasDeletionScheduledWorkspaces.dormant_at
) {
return `This workspace has been dormant for ${formatDistanceToNow(
Date.parse(hasDeletionScheduledWorkspaces.locked_at),
Date.parse(hasDeletionScheduledWorkspaces.dormant_at),
)} and is scheduled to be deleted on ${formatDate(
hasDeletionScheduledWorkspaces.deleting_at,
)} . To keep it you must activate the workspace.`
} else if (hasLockedWorkspaces && hasLockedWorkspaces.locked_at) {
} else if (hasDormantWorkspaces && hasDormantWorkspaces.dormant_at) {
return `This workspace has been dormant for ${formatDistanceToNow(
Date.parse(hasLockedWorkspaces.locked_at),
Date.parse(hasDormantWorkspaces.dormant_at),
)}
and cannot be interacted
with. Dormant workspaces are eligible for
@@ -88,7 +88,7 @@ export const DormantWorkspaceBanner = ({
<span>There are</span>{" "}
<Link
component={RouterLink}
to="/workspaces?filter=locked_at:1970-01-01"
to="/workspaces?filter=dormant_at:1970-01-01"
>
workspaces
</Link>{" "}
@@ -1,4 +1,3 @@
export * from "./ImpendingDeletionStat"
export * from "./ImpendingDeletionBadge"
export * from "./ImpendingDeletionText"
export * from "./ImpendingDeletionBanner"
@@ -73,7 +73,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
allow_user_cancel_workspace_jobs:
template.allow_user_cancel_workspace_jobs,
update_workspace_last_used_at: false,
update_workspace_locked_at: false,
update_workspace_dormant_at: false,
},
validationSchema,
onSubmit,
@@ -31,10 +31,10 @@ const validFormValues: FormValues = {
weeks: 1,
},
failure_ttl_ms: 0,
inactivity_ttl_ms: 0,
locked_ttl_ms: 0,
time_til_dormant_ms: 0,
time_til_dormant_autodelete_ms: 0,
update_workspace_last_used_at: false,
update_workspace_locked_at: false,
update_workspace_dormant_at: false,
}
const renderTemplateSettingsPage = async () => {
@@ -1,19 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react"
import { InactivityDialog } from "./InactivityDialog"
const meta: Meta<typeof InactivityDialog> = {
title: "InactivityDialog",
component: InactivityDialog,
}
export default meta
type Story = StoryObj<typeof InactivityDialog>
export const OpenDialog: Story = {
args: {
submitValues: () => null,
isInactivityDialogOpen: true,
setIsInactivityDialogOpen: () => null,
workspacesToBeLockedToday: 2,
},
}
@@ -1,59 +0,0 @@
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"
export const InactivityDialog = ({
submitValues,
isInactivityDialogOpen,
setIsInactivityDialogOpen,
workspacesToBeLockedToday,
}: {
submitValues: () => void
isInactivityDialogOpen: boolean
setIsInactivityDialogOpen: (arg0: boolean) => void
workspacesToBeLockedToday: number
}) => {
return (
<ConfirmDialog
type="delete"
open={isInactivityDialogOpen}
onConfirm={() => {
submitValues()
setIsInactivityDialogOpen(false)
}}
onClose={() => setIsInactivityDialogOpen(false)}
title="Lock inactive workspaces"
confirmText="Lock Workspaces"
description={`There are ${
workspacesToBeLockedToday ? workspacesToBeLockedToday : ""
} workspaces that already match this filter and will be locked upon form submission. Are you sure you want to proceed?`}
/>
)
}
export const DeleteLockedDialog = ({
submitValues,
isLockedDialogOpen,
setIsLockedDialogOpen,
workspacesToBeDeletedToday,
}: {
submitValues: () => void
isLockedDialogOpen: boolean
setIsLockedDialogOpen: (arg0: boolean) => void
workspacesToBeDeletedToday: number
}) => {
return (
<ConfirmDialog
type="delete"
open={isLockedDialogOpen}
onConfirm={() => {
submitValues()
setIsLockedDialogOpen(false)
}}
onClose={() => setIsLockedDialogOpen(false)}
title="Delete Locked Workspaces"
confirmText="Delete Workspaces"
description={`There are ${
workspacesToBeDeletedToday ? workspacesToBeDeletedToday : ""
} workspaces that already match this filter and will be deleted upon form submission. Are you sure you want to proceed?`}
/>
)
}
@@ -17,7 +17,7 @@ import Checkbox from "@mui/material/Checkbox"
import FormControlLabel from "@mui/material/FormControlLabel"
import Switch from "@mui/material/Switch"
import {
useWorkspacesToBeLocked,
useWorkspacesToGoDormant,
useWorkspacesToBeDeleted,
} from "./useWorkspacesToBeDeleted"
import { TemplateScheduleFormValues, getValidationSchema } from "./formHelpers"
@@ -29,7 +29,7 @@ const MS_HOUR_CONVERSION = 3600000
const MS_DAY_CONVERSION = 86400000
const FAILURE_CLEANUP_DEFAULT = 7
const INACTIVITY_CLEANUP_DEFAULT = 180
const LOCKED_CLEANUP_DEFAULT = 30
const DORMANT_AUTODELETION_DEFAULT = 30
export interface TemplateScheduleForm {
template: Template
@@ -67,11 +67,11 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
failure_ttl_ms: allowAdvancedScheduling
? template.failure_ttl_ms / MS_DAY_CONVERSION
: 0,
inactivity_ttl_ms: allowAdvancedScheduling
? template.inactivity_ttl_ms / MS_DAY_CONVERSION
time_til_dormant_ms: allowAdvancedScheduling
? template.time_til_dormant_ms / MS_DAY_CONVERSION
: 0,
locked_ttl_ms: allowAdvancedScheduling
? template.locked_ttl_ms / MS_DAY_CONVERSION
time_til_dormant_autodelete_ms: allowAdvancedScheduling
? template.time_til_dormant_autodelete_ms / MS_DAY_CONVERSION
: 0,
restart_requirement: {
@@ -84,18 +84,21 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
failure_cleanup_enabled:
allowAdvancedScheduling && Boolean(template.failure_ttl_ms),
inactivity_cleanup_enabled:
allowAdvancedScheduling && Boolean(template.inactivity_ttl_ms),
locked_cleanup_enabled:
allowAdvancedScheduling && Boolean(template.locked_ttl_ms),
allowAdvancedScheduling && Boolean(template.time_til_dormant_ms),
dormant_autodeletion_cleanup_enabled:
allowAdvancedScheduling &&
Boolean(template.time_til_dormant_autodelete_ms),
update_workspace_last_used_at: false,
update_workspace_locked_at: false,
update_workspace_dormant_at: false,
},
validationSchema,
onSubmit: () => {
const dormancyChanged =
form.initialValues.inactivity_ttl_ms !== form.values.inactivity_ttl_ms
form.initialValues.time_til_dormant_ms !==
form.values.time_til_dormant_ms
const deletionChanged =
form.initialValues.locked_ttl_ms !== form.values.locked_ttl_ms
form.initialValues.time_til_dormant_autodelete_ms !==
form.values.time_til_dormant_autodelete_ms
const dormancyScheduleChanged =
form.values.inactivity_cleanup_enabled &&
@@ -128,13 +131,13 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
const weekFromNow = new Date(now)
weekFromNow.setDate(now.getDate() + 7)
const workspacesToDormancyNow = useWorkspacesToBeLocked(
const workspacesToDormancyNow = useWorkspacesToGoDormant(
template,
form.values,
now,
)
const workspacesToDormancyInWeek = useWorkspacesToBeLocked(
const workspacesToDormancyInWeek = useWorkspacesToGoDormant(
template,
form.values,
weekFromNow,
@@ -175,17 +178,17 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
failure_ttl_ms: form.values.failure_ttl_ms
? form.values.failure_ttl_ms * MS_DAY_CONVERSION
: undefined,
inactivity_ttl_ms: form.values.inactivity_ttl_ms
? form.values.inactivity_ttl_ms * MS_DAY_CONVERSION
time_til_dormant_ms: form.values.time_til_dormant_ms
? form.values.time_til_dormant_ms * MS_DAY_CONVERSION
: undefined,
locked_ttl_ms: form.values.locked_ttl_ms
? form.values.locked_ttl_ms * MS_DAY_CONVERSION
time_til_dormant_autodelete_ms: form.values.time_til_dormant_autodelete_ms
? form.values.time_til_dormant_autodelete_ms * MS_DAY_CONVERSION
: undefined,
allow_user_autostart: form.values.allow_user_autostart,
allow_user_autostop: form.values.allow_user_autostop,
update_workspace_last_used_at: form.values.update_workspace_last_used_at,
update_workspace_locked_at: form.values.update_workspace_locked_at,
update_workspace_dormant_at: form.values.update_workspace_dormant_at,
})
}
@@ -211,37 +214,37 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
const handleToggleInactivityCleanup = async (e: ChangeEvent) => {
form.handleChange(e)
if (!form.values.inactivity_cleanup_enabled) {
// fill inactivity_ttl_ms with defaults
// fill time_til_dormant_ms with defaults
await form.setValues({
...form.values,
inactivity_cleanup_enabled: true,
inactivity_ttl_ms: INACTIVITY_CLEANUP_DEFAULT,
time_til_dormant_ms: INACTIVITY_CLEANUP_DEFAULT,
})
} else {
// clear inactivity_ttl_ms
// clear time_til_dormant_ms
await form.setValues({
...form.values,
inactivity_cleanup_enabled: false,
inactivity_ttl_ms: 0,
time_til_dormant_ms: 0,
})
}
}
const handleToggleLockedCleanup = async (e: ChangeEvent) => {
const handleToggleDormantAutoDeletion = async (e: ChangeEvent) => {
form.handleChange(e)
if (!form.values.locked_cleanup_enabled) {
if (!form.values.dormant_autodeletion_cleanup_enabled) {
// fill failure_ttl_ms with defaults
await form.setValues({
...form.values,
locked_cleanup_enabled: true,
locked_ttl_ms: LOCKED_CLEANUP_DEFAULT,
dormant_autodeletion_cleanup_enabled: true,
time_til_dormant_autodelete_ms: DORMANT_AUTODELETION_DEFAULT,
})
} else {
// clear failure_ttl_ms
await form.setValues({
...form.values,
locked_cleanup_enabled: false,
locked_ttl_ms: 0,
dormant_autodeletion_cleanup_enabled: false,
time_til_dormant_autodelete_ms: 0,
})
}
}
@@ -398,10 +401,10 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
/>
<TextField
{...getFieldHelpers(
"inactivity_ttl_ms",
"time_til_dormant_ms",
<TTLHelperText
translationName="dormancyThresholdHelperText"
ttl={form.values.inactivity_ttl_ms}
ttl={form.values.time_til_dormant_ms}
/>,
)}
disabled={
@@ -423,21 +426,24 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
control={
<Switch
name="dormancyAutoDeletion"
checked={form.values.locked_cleanup_enabled}
onChange={handleToggleLockedCleanup}
checked={form.values.dormant_autodeletion_cleanup_enabled}
onChange={handleToggleDormantAutoDeletion}
/>
}
label="Enable Dormancy Auto-Deletion"
/>
<TextField
{...getFieldHelpers(
"locked_ttl_ms",
"time_til_dormant_autodelete_ms",
<TTLHelperText
translationName="dormancyAutoDeletionHelperText"
ttl={form.values.locked_ttl_ms}
ttl={form.values.time_til_dormant_autodelete_ms}
/>,
)}
disabled={isSubmitting || !form.values.locked_cleanup_enabled}
disabled={
isSubmitting ||
!form.values.dormant_autodeletion_cleanup_enabled
}
fullWidth
inputProps={{ min: 0, step: "any" }}
label="Time until deletion (days)"
@@ -455,7 +461,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
// These fields are request-scoped so they should be reset
// after every submission.
form
.setFieldValue("update_workspace_locked_at", false)
.setFieldValue("update_workspace_dormant_at", false)
.catch((error) => {
throw error
})
@@ -478,18 +484,19 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
setIsScheduleDialogOpen(false)
}}
title="Workspace Scheduling"
updateLockedWorkspaces={(update: boolean) =>
form.setFieldValue("update_workspace_locked_at", update)
updateDormantWorkspaces={(update: boolean) =>
form.setFieldValue("update_workspace_dormant_at", update)
}
updateInactiveWorkspaces={(update: boolean) =>
form.setFieldValue("update_workspace_last_used_at", update)
}
dormantValueChanged={
form.initialValues.inactivity_ttl_ms !==
form.values.inactivity_ttl_ms
form.initialValues.time_til_dormant_ms !==
form.values.time_til_dormant_ms
}
deletionValueChanged={
form.initialValues.locked_ttl_ms !== form.values.locked_ttl_ms
form.initialValues.time_til_dormant_autodelete_ms !==
form.values.time_til_dormant_autodelete_ms
}
/>
)}
@@ -5,7 +5,7 @@ import i18next from "i18next"
export interface TemplateScheduleFormValues extends UpdateTemplateMeta {
failure_cleanup_enabled: boolean
inactivity_cleanup_enabled: boolean
locked_cleanup_enabled: boolean
dormant_autodeletion_cleanup_enabled: boolean
}
const MAX_TTL_DAYS = 30
@@ -50,7 +50,7 @@ export const getValidationSchema = (): Yup.AnyObjectSchema =>
}
},
),
inactivity_ttl_ms: Yup.number()
time_til_dormant_ms: Yup.number()
.min(0, "Dormancy threshold days must not be less than 0.")
.test(
"positive-if-enabled",
@@ -64,14 +64,14 @@ export const getValidationSchema = (): Yup.AnyObjectSchema =>
}
},
),
locked_ttl_ms: Yup.number()
time_til_dormant_autodelete_ms: Yup.number()
.min(0, "Dormancy auto-deletion days must not be less than 0.")
.test(
"positive-if-enabled",
"Dormancy auto-deletion days must be greater than zero when enabled.",
function (value) {
const parent = this.parent as TemplateScheduleFormValues
if (parent.locked_cleanup_enabled) {
if (parent.dormant_autodeletion_cleanup_enabled) {
return Boolean(value)
} else {
return true
@@ -3,7 +3,7 @@ import { Workspace, Template } from "api/typesGenerated"
import { TemplateScheduleFormValues } from "./formHelpers"
import { useWorkspacesData } from "pages/WorkspacesPage/data"
export const useWorkspacesToBeLocked = (
export const useWorkspacesToGoDormant = (
template: Template,
formValues: TemplateScheduleFormValues,
fromDate: Date,
@@ -15,17 +15,17 @@ export const useWorkspacesToBeLocked = (
})
return data?.workspaces?.filter((workspace: Workspace) => {
if (!formValues.inactivity_ttl_ms) {
if (!formValues.time_til_dormant_ms) {
return
}
if (workspace.locked_at) {
if (workspace.dormant_at) {
return
}
const proposedLocking = new Date(
new Date(workspace.last_used_at).getTime() +
formValues.inactivity_ttl_ms * DayInMS,
formValues.time_til_dormant_ms * DayInMS,
)
if (compareAsc(proposedLocking, fromDate) < 1) {
@@ -44,16 +44,16 @@ export const useWorkspacesToBeDeleted = (
const { data } = useWorkspacesData({
page: 0,
limit: 0,
query: "template:" + template.name + " locked_at:1970-01-01",
query: "template:" + template.name + " dormant_at:1970-01-01",
})
return data?.workspaces?.filter((workspace: Workspace) => {
if (!workspace.locked_at || !formValues.locked_ttl_ms) {
if (!workspace.dormant_at || !formValues.time_til_dormant_autodelete_ms) {
return false
}
const proposedLocking = new Date(
new Date(workspace.locked_at).getTime() +
formValues.locked_ttl_ms * DayInMS,
new Date(workspace.dormant_at).getTime() +
formValues.time_til_dormant_autodelete_ms * DayInMS,
)
if (compareAsc(proposedLocking, fromDate) < 1) {
@@ -21,10 +21,10 @@ const validFormValues = {
default_ttl_ms: 1,
max_ttl_ms: 2,
failure_ttl_ms: 7,
inactivity_ttl_ms: 180,
locked_ttl_ms: 30,
time_til_dormant_ms: 180,
time_til_dormant_autodelete_ms: 30,
update_workspace_last_used_at: false,
update_workspace_locked_at: false,
update_workspace_dormant_at: false,
}
const renderTemplateSchedulePage = async () => {
@@ -39,14 +39,14 @@ const fillAndSubmitForm = async ({
default_ttl_ms,
max_ttl_ms,
failure_ttl_ms,
inactivity_ttl_ms,
locked_ttl_ms,
time_til_dormant_ms,
time_til_dormant_autodelete_ms,
}: {
default_ttl_ms: number
max_ttl_ms: number
failure_ttl_ms: number
inactivity_ttl_ms: number
locked_ttl_ms: number
time_til_dormant_ms: number
time_til_dormant_autodelete_ms: number
}) => {
const user = userEvent.setup()
const defaultTtlLabel = t("defaultTtlLabel", { ns: "templateSettingsPage" })
@@ -67,19 +67,22 @@ const fillAndSubmitForm = async ({
const inactivityTtlField = screen.getByRole("checkbox", {
name: /Dormancy Threshold/i,
})
await user.type(inactivityTtlField, inactivity_ttl_ms.toString())
await user.type(inactivityTtlField, time_til_dormant_ms.toString())
const lockedTtlField = screen.getByRole("checkbox", {
const dormancyAutoDeletionField = screen.getByRole("checkbox", {
name: /Dormancy Auto-Deletion/i,
})
await user.type(lockedTtlField, locked_ttl_ms.toString())
await user.type(
dormancyAutoDeletionField,
time_til_dormant_autodelete_ms.toString(),
)
const submitButton = await screen.findByText(
FooterFormLanguage.defaultSubmitLabel,
)
await user.click(submitButton)
// User needs to confirm inactivity and locked ttl
// User needs to confirm dormancy and autodeletion fields.
const confirmButton = await screen.findByTestId("confirm-button")
await user.click(confirmButton)
}
@@ -140,8 +143,9 @@ describe("TemplateSchedulePage", () => {
"test-template",
expect.objectContaining({
failure_ttl_ms: validFormValues.failure_ttl_ms * 86400000,
inactivity_ttl_ms: validFormValues.inactivity_ttl_ms * 86400000,
locked_ttl_ms: validFormValues.locked_ttl_ms * 86400000,
time_til_dormant_ms: validFormValues.time_til_dormant_ms * 86400000,
time_til_dormant_autodelete_ms:
validFormValues.time_til_dormant_autodelete_ms * 86400000,
}),
),
)
@@ -217,7 +221,7 @@ describe("TemplateSchedulePage", () => {
it("allows an inactivity ttl of 7 days", () => {
const values: UpdateTemplateMeta = {
...validFormValues,
inactivity_ttl_ms: 86400000 * 7,
time_til_dormant_ms: 86400000 * 7,
}
const validate = () => getValidationSchema().validateSync(values)
expect(validate).not.toThrowError()
@@ -226,7 +230,7 @@ describe("TemplateSchedulePage", () => {
it("allows an inactivity ttl of 0", () => {
const values: UpdateTemplateMeta = {
...validFormValues,
inactivity_ttl_ms: 0,
time_til_dormant_ms: 0,
}
const validate = () => getValidationSchema().validateSync(values)
expect(validate).not.toThrowError()
@@ -235,7 +239,7 @@ describe("TemplateSchedulePage", () => {
it("disallows a negative inactivity ttl", () => {
const values: UpdateTemplateMeta = {
...validFormValues,
inactivity_ttl_ms: -1,
time_til_dormant_ms: -1,
}
const validate = () => getValidationSchema().validateSync(values)
expect(validate).toThrowError(
@@ -246,7 +250,7 @@ describe("TemplateSchedulePage", () => {
it("allows a dormancy ttl of 7 days", () => {
const values: UpdateTemplateMeta = {
...validFormValues,
locked_ttl_ms: 86400000 * 7,
time_til_dormant_autodelete_ms: 86400000 * 7,
}
const validate = () => getValidationSchema().validateSync(values)
expect(validate).not.toThrowError()
@@ -255,7 +259,7 @@ describe("TemplateSchedulePage", () => {
it("allows a dormancy ttl of 0", () => {
const values: UpdateTemplateMeta = {
...validFormValues,
locked_ttl_ms: 0,
time_til_dormant_autodelete_ms: 0,
}
const validate = () => getValidationSchema().validateSync(values)
expect(validate).not.toThrowError()
@@ -264,7 +268,7 @@ describe("TemplateSchedulePage", () => {
it("disallows a negative inactivity ttl", () => {
const values: UpdateTemplateMeta = {
...validFormValues,
locked_ttl_ms: -1,
time_til_dormant_autodelete_ms: -1,
}
const validate = () => getValidationSchema().validateSync(values)
expect(validate).toThrowError(
@@ -23,7 +23,7 @@ import { displayError } from "components/GlobalSnackbar/utils"
import { getErrorMessage } from "api/errors"
const WorkspacesPage: FC = () => {
const [lockedWorkspaces, setLockedWorkspaces] = useState<Workspace[]>([])
const [dormantWorkspaces, setDormantWorkspaces] = useState<Workspace[]>([])
// If we use a useSearchParams for each hook, the values will not be in sync.
// So we have to use a single one, centralizing the values, and pass it to
// each hook.
@@ -36,23 +36,23 @@ const WorkspacesPage: FC = () => {
})
const experimentEnabled = useIsWorkspaceActionsEnabled()
// If workspace actions are enabled we need to fetch the locked
// If workspace actions are enabled we need to fetch the dormant
// workspaces as well. This lets us determine whether we should
// show a banner to the user indicating that some of their workspaces
// are at risk of being deleted.
useEffect(() => {
if (experimentEnabled) {
const includesLocked = filterProps.filter.query.includes("locked_at")
const lockedQuery = includesLocked
const includesDormant = filterProps.filter.query.includes("dormant_at")
const dormantQuery = includesDormant
? filterProps.filter.query
: filterProps.filter.query + " locked_at:1970-01-01"
: filterProps.filter.query + " dormant_at:1970-01-01"
if (includesLocked && data) {
setLockedWorkspaces(data.workspaces)
if (includesDormant && data) {
setDormantWorkspaces(data.workspaces)
} else {
getWorkspaces({ q: lockedQuery })
getWorkspaces({ q: dormantQuery })
.then((resp) => {
setLockedWorkspaces(resp.workspaces)
setDormantWorkspaces(resp.workspaces)
})
.catch(() => {
// TODO
@@ -60,8 +60,8 @@ const WorkspacesPage: FC = () => {
}
} else {
// If the experiment isn't included then we'll pretend
// like locked workspaces don't exist.
setLockedWorkspaces([])
// like dormant workspaces don't exist.
setDormantWorkspaces([])
}
}, [experimentEnabled, data, filterProps.filter.query])
const updateWorkspace = useWorkspaceUpdate(queryKey)
@@ -90,7 +90,7 @@ const WorkspacesPage: FC = () => {
checkedWorkspaces={checkedWorkspaces}
onCheckChange={setCheckedWorkspaces}
workspaces={data?.workspaces}
lockedWorkspaces={lockedWorkspaces}
dormantWorkspaces={dormantWorkspaces}
error={error}
count={data?.count}
page={pagination.page}
@@ -38,7 +38,7 @@ export const Language = {
export interface WorkspacesPageViewProps {
error: unknown
workspaces?: Workspace[]
lockedWorkspaces?: Workspace[]
dormantWorkspaces?: Workspace[]
checkedWorkspaces: Workspace[]
count?: number
filterProps: ComponentProps<typeof WorkspacesFilter>
@@ -55,7 +55,7 @@ export const WorkspacesPageView: FC<
React.PropsWithChildren<WorkspacesPageViewProps>
> = ({
workspaces,
lockedWorkspaces,
dormantWorkspaces,
error,
limit,
count,
@@ -70,12 +70,12 @@ export const WorkspacesPageView: FC<
}) => {
const { saveLocal } = useLocalStorage()
const workspacesDeletionScheduled = lockedWorkspaces
const workspacesDeletionScheduled = dormantWorkspaces
?.filter((workspace) => workspace.deleting_at)
.map((workspace) => workspace.id)
const hasLockedWorkspace =
lockedWorkspaces !== undefined && lockedWorkspaces.length > 0
const hasDormantWorkspace =
dormantWorkspaces !== undefined && dormantWorkspaces.length > 0
return (
<Margins>
@@ -102,8 +102,8 @@ export const WorkspacesPageView: FC<
</Maybe>
{/* <ImpendingDeletionBanner/> determines its own visibility */}
<DormantWorkspaceBanner
workspaces={lockedWorkspaces}
shouldRedisplayBanner={hasLockedWorkspace}
workspaces={dormantWorkspaces}
shouldRedisplayBanner={hasDormantWorkspace}
onDismiss={() =>
saveLocal(
"dismissedWorkspaceList",
@@ -47,8 +47,8 @@ export const WorkspacesFilter = ({
const presets = [...PRESET_FILTERS]
if (useIsWorkspaceActionsEnabled()) {
presets.push({
query: workspaceFilterQuery.locked,
name: "Locked workspaces",
query: workspaceFilterQuery.dormant,
name: "Dormant workspaces",
})
}
+2 -2
View File
@@ -445,8 +445,8 @@ export const MockTemplate: TypesGen.Template = {
icon: "/icon/code.svg",
allow_user_cancel_workspace_jobs: true,
failure_ttl_ms: 0,
inactivity_ttl_ms: 0,
locked_ttl_ms: 0,
time_til_dormant_ms: 0,
time_til_dormant_autodelete_ms: 0,
allow_user_autostart: false,
allow_user_autostop: false,
}
+1 -1
View File
@@ -14,7 +14,7 @@ export const workspaceFilterQuery = {
all: "",
running: "status:running",
failed: "status:failed",
locked: "locked_at:1970-01-01",
dormant: "dormant_at:1970-01-01",
}
export const userFilterQuery = {
@@ -697,7 +697,7 @@ export const workspaceMachine = createMachine(
},
activateWorkspace: (context) => async (send) => {
if (context.workspace) {
const activateWorkspacePromise = await API.updateWorkspaceLock(
const activateWorkspacePromise = await API.updateWorkspaceDormancy(
context.workspace.id,
false,
)