diff --git a/site/src/components/WorkspaceDeletion/ImpendingDeletionBanner.tsx b/site/src/components/WorkspaceDeletion/ImpendingDeletionBanner.tsx index d793c16d5d..75460ef1d2 100644 --- a/site/src/components/WorkspaceDeletion/ImpendingDeletionBanner.tsx +++ b/site/src/components/WorkspaceDeletion/ImpendingDeletionBanner.tsx @@ -1,5 +1,8 @@ import { Workspace } from "api/typesGenerated" -import { displayImpendingDeletion } from "./utils" +import { + displayImpendingDeletion, + IMPENDING_DELETION_DISPLAY_THRESHOLD, +} from "./utils" import { useDashboard } from "components/Dashboard/DashboardProvider" import { Alert } from "components/Alert/Alert" import { formatDistanceToNow, differenceInDays, add, format } from "date-fns" @@ -48,7 +51,9 @@ export const ImpendingDeletionBanner = ({ new Date(), ) - const plusFourteen = add(new Date(), { days: 14 }) + const plusFourteen = add(new Date(), { + days: IMPENDING_DELETION_DISPLAY_THRESHOLD, + }) return ( null, isInactivityDialogOpen: true, setIsInactivityDialogOpen: () => null, - workspacesToBeDeletedToday: 2, + numberWorkspacesToBeDeletedToday: 2, }, } diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm/InactivityDialog.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm/InactivityDialog.tsx index 3f5ec252b0..192a0dd4c5 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm/InactivityDialog.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm/InactivityDialog.tsx @@ -1,16 +1,19 @@ import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog" +import { useTranslation } from "react-i18next" export const InactivityDialog = ({ submitValues, isInactivityDialogOpen, setIsInactivityDialogOpen, - workspacesToBeDeletedToday, + numberWorkspacesToBeDeletedToday, }: { submitValues: () => void isInactivityDialogOpen: boolean setIsInactivityDialogOpen: (arg0: boolean) => void - workspacesToBeDeletedToday: number + numberWorkspacesToBeDeletedToday: number }) => { + const { t } = useTranslation("templateSettingsPage") + return ( setIsInactivityDialogOpen(false)} title="Delete inactive 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?`} + description={t("inactivityDialogDescription", { + count: numberWorkspacesToBeDeletedToday, + })} /> ) } diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm/TemplateScheduleForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm/TemplateScheduleForm.tsx index 5a0a4f819e..7c382fe9b9 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm/TemplateScheduleForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm/TemplateScheduleForm.tsx @@ -82,7 +82,7 @@ export const TemplateScheduleForm: FC = ({ validationSchema, onSubmit: () => { if ( - form.values.inactivity_cleanup_enabled && + form.values.locked_cleanup_enabled && workspacesToBeDeletedToday && workspacesToBeDeletedToday.length > 0 ) { @@ -100,7 +100,10 @@ export const TemplateScheduleForm: FC = ({ const { t } = useTranslation("templateSettingsPage") const styles = useStyles() - const workspacesToBeDeletedToday = useWorkspacesToBeDeleted(form.values) + const workspacesToBeDeletedToday = useWorkspacesToBeDeleted( + form.values, + template.name, + ) const [isInactivityDialogOpen, setIsInactivityDialogOpen] = useState(false) @@ -305,6 +308,7 @@ export const TemplateScheduleForm: FC = ({ onChange={handleToggleFailureCleanup} /> } + disabled={isSubmitting} label="Enable Failure Cleanup" /> = ({ onChange={handleToggleInactivityCleanup} /> } + disabled={isSubmitting} label="Enable Inactivity Cleanup" /> = ({ onChange={handleToggleLockedCleanup} /> } + disabled={ + isSubmitting || !form.values.inactivity_cleanup_enabled + } label="Enable Locked Cleanup" /> = ({ submitValues={submitValues} isInactivityDialogOpen={isInactivityDialogOpen} setIsInactivityDialogOpen={setIsInactivityDialogOpen} - workspacesToBeDeletedToday={workspacesToBeDeletedToday?.length ?? 0} + numberWorkspacesToBeDeletedToday={ + workspacesToBeDeletedToday?.length ?? 0 + } /> { const { data: workspacesData } = useQuery({ queryKey: ["workspaces"], - queryFn: () => getWorkspaces({}), - enabled: formValues.inactivity_cleanup_enabled, + queryFn: () => getWorkspaces({ q: `template:${templateName}` }), + enabled: formValues.locked_cleanup_enabled, }) return workspacesData?.workspaces?.filter((workspace: Workspace) => { const isInactive = inactiveStatuses.includes(workspace.latest_build.status) diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index 440fa1c8fe..93bb9195fb 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -2,7 +2,11 @@ import { usePagination } from "hooks/usePagination" import { FC } from "react" import { Helmet } from "react-helmet-async" import { pageTitle } from "utils/page" -import { useWorkspacesData, useWorkspaceUpdate } from "./data" +import { + useWorkspacesData, + useWorkspaceUpdate, + useWorkspacesWithImpendingDeletions, +} from "./data" import { WorkspacesPageView } from "./WorkspacesPageView" import { useOrganizationId, usePermissions } from "hooks" import { useTemplateFilterMenu, useStatusFilterMenu } from "./filter/menus" @@ -29,6 +33,9 @@ const WorkspacesPage: FC = () => { query: filter.query, }) const updateWorkspace = useWorkspaceUpdate(queryKey) + const { data: workspacesWithImpendingDeletions } = + useWorkspacesWithImpendingDeletions() + const permissions = usePermissions() const canFilterByUser = permissions.viewDeploymentValues const userMenu = useUserFilterMenu({ @@ -57,6 +64,7 @@ const WorkspacesPage: FC = () => { page: number @@ -45,6 +46,7 @@ export const WorkspacesPageView: FC< React.PropsWithChildren > = ({ workspaces, + workspacesWithDeletions, error, limit, count, @@ -55,9 +57,9 @@ export const WorkspacesPageView: FC< }) => { const { saveLocal, getLocal } = useLocalStorage() - const workspaceIdsWithImpendingDeletions = workspaces - ?.filter((workspace) => workspace.deleting_at) - .map((workspace) => workspace.id) + const workspaceIdsWithImpendingDeletions = workspacesWithDeletions?.map( + (workspace) => workspace.id, + ) /** * Returns a boolean indicating if there are workspaces that have been @@ -105,7 +107,9 @@ export const WorkspacesPageView: FC< {/* determines its own visibility */} workspace.deleting_at)} + workspace={workspacesWithDeletions?.find( + (workspace) => workspace.deleting_at, + )} shouldRedisplayBanner={isNewWorkspacesImpendingDeletion()} onDismiss={() => saveLocal( diff --git a/site/src/pages/WorkspacesPage/data.ts b/site/src/pages/WorkspacesPage/data.ts index e3d71ab808..c681576c04 100644 --- a/site/src/pages/WorkspacesPage/data.ts +++ b/site/src/pages/WorkspacesPage/data.ts @@ -14,6 +14,8 @@ import { import { displayError } from "components/GlobalSnackbar/utils" import { useState } from "react" import { useTranslation } from "react-i18next" +import { add, format } from "date-fns" +import { IMPENDING_DELETION_DISPLAY_THRESHOLD } from "components/WorkspaceDeletion/utils" type UseWorkspacesDataParams = { page: number @@ -79,6 +81,21 @@ export const useWorkspaceUpdate = (queryKey: QueryKey) => { }) } +// Returns workspace that will be deleted within 14 days. +// This query is used for the ImpendingDeletionBanner +export const useWorkspacesWithImpendingDeletions = () => { + return useQuery({ + queryKey: ["workspacesWithImpendingDeletions"], + queryFn: () => + getWorkspaces({ + q: `deleting_by:"${format( + add(new Date(), { days: IMPENDING_DELETION_DISPLAY_THRESHOLD }), + "yyyy-MM-dd", + )}"`, + }), + }) +} + const assignLatestBuild = ( oldResponse: WorkspacesResponse, build: WorkspaceBuild,