mirror of
https://github.com/coder/coder.git
synced 2026-10-25 09:42:55 +08:00
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:
+5
-5
@@ -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
|
||||
|
||||
Generated
+10
-10
@@ -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"
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+3
-3
@@ -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 () => {
|
||||
|
||||
-19
@@ -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,
|
||||
},
|
||||
}
|
||||
-59
@@ -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?`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
+49
-42
@@ -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
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
+4
-4
@@ -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
|
||||
|
||||
+8
-8
@@ -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) {
|
||||
|
||||
+23
-19
@@ -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",
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user