mirror of https://github.com/coder/coder.git
fix: add front-end fixes for minor workspace action bugs (#8252)
* fix: incorrect copy on inactivity_ttl field * disabling locked fields unless inactivity TTL is set * scoping inactivity dialog message to template * fixed pluralization for inactivity dialog * amending logic gate to show inactivity dialog * fixed pagination bug
This commit is contained in:
parent
9f76dab348
commit
c569528fb7
|
@ -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 (
|
||||
<Alert
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Workspace } from "api/typesGenerated"
|
|||
|
||||
// This const dictates how far out we alert the user that a workspace
|
||||
// has an impending deletion (due to template.InactivityTTL being set)
|
||||
const IMPENDING_DELETION_DISPLAY_THRESHOLD = 14 // 14 days
|
||||
export const IMPENDING_DELETION_DISPLAY_THRESHOLD = 14 // 14 days
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating if an impending deletion indicator should be
|
||||
|
|
|
@ -22,12 +22,14 @@
|
|||
"failureTTLHelperText_zero": "Coder will not automatically stop failed workspaces",
|
||||
"failureTTLHelperText_one": "Coder will attempt to stop failed workspaces after {{count}} day.",
|
||||
"failureTTLHelperText_other": "Coder will attempt to 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.",
|
||||
"inactivityTTLHelperText_zero": "Coder will not automatically lock inactive workspaces",
|
||||
"inactivityTTLHelperText_one": "Coder will automatically lock inactive workspaces after {{count}} day.",
|
||||
"inactivityTTLHelperText_other": "Coder will automatically lock inactive workspaces after {{count}} days.",
|
||||
"lockedTTLHelperText_zero": "Coder will not automatically delete locked workspaces",
|
||||
"lockedTTLHelperText_one": "Coder will automatically delete locked workspaces after {{count}} day.",
|
||||
"lockedTTLHelperText_other": "Coder will automatically delete locked workspaces after {{count}} days.",
|
||||
"inactivityDialogDescription_one": "There is {{count}} workspace that already matches this filter and will be deleted upon form submission. Are you sure you want to proceed?",
|
||||
"inactivityDialogDescription_other": "There are {{count}} workspaces that already match this filter and will be deleted upon form submission. Are you sure you want to proceed?",
|
||||
"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.",
|
||||
|
|
|
@ -14,6 +14,6 @@ export const OpenDialog: Story = {
|
|||
submitValues: () => null,
|
||||
isInactivityDialogOpen: true,
|
||||
setIsInactivityDialogOpen: () => null,
|
||||
workspacesToBeDeletedToday: 2,
|
||||
numberWorkspacesToBeDeletedToday: 2,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<ConfirmDialog
|
||||
type="delete"
|
||||
|
@ -22,9 +25,9 @@ export const InactivityDialog = ({
|
|||
onClose={() => 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,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
|||
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<TemplateScheduleForm> = ({
|
|||
const { t } = useTranslation("templateSettingsPage")
|
||||
const styles = useStyles()
|
||||
|
||||
const workspacesToBeDeletedToday = useWorkspacesToBeDeleted(form.values)
|
||||
const workspacesToBeDeletedToday = useWorkspacesToBeDeleted(
|
||||
form.values,
|
||||
template.name,
|
||||
)
|
||||
|
||||
const [isInactivityDialogOpen, setIsInactivityDialogOpen] =
|
||||
useState<boolean>(false)
|
||||
|
@ -305,6 +308,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
|||
onChange={handleToggleFailureCleanup}
|
||||
/>
|
||||
}
|
||||
disabled={isSubmitting}
|
||||
label="Enable Failure Cleanup"
|
||||
/>
|
||||
<TextField
|
||||
|
@ -337,6 +341,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
|||
onChange={handleToggleInactivityCleanup}
|
||||
/>
|
||||
}
|
||||
disabled={isSubmitting}
|
||||
label="Enable Inactivity Cleanup"
|
||||
/>
|
||||
<TextField
|
||||
|
@ -371,6 +376,9 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
|||
onChange={handleToggleLockedCleanup}
|
||||
/>
|
||||
}
|
||||
disabled={
|
||||
isSubmitting || !form.values.inactivity_cleanup_enabled
|
||||
}
|
||||
label="Enable Locked Cleanup"
|
||||
/>
|
||||
<TextField
|
||||
|
@ -396,7 +404,9 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
|||
submitValues={submitValues}
|
||||
isInactivityDialogOpen={isInactivityDialogOpen}
|
||||
setIsInactivityDialogOpen={setIsInactivityDialogOpen}
|
||||
workspacesToBeDeletedToday={workspacesToBeDeletedToday?.length ?? 0}
|
||||
numberWorkspacesToBeDeletedToday={
|
||||
workspacesToBeDeletedToday?.length ?? 0
|
||||
}
|
||||
/>
|
||||
<FormFooter
|
||||
onCancel={onCancel}
|
||||
|
|
|
@ -13,11 +13,12 @@ const inactiveStatuses: WorkspaceStatus[] = [
|
|||
|
||||
export const useWorkspacesToBeDeleted = (
|
||||
formValues: TemplateScheduleFormValues,
|
||||
templateName: string,
|
||||
) => {
|
||||
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)
|
||||
|
|
|
@ -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 = () => {
|
|||
|
||||
<WorkspacesPageView
|
||||
workspaces={data?.workspaces}
|
||||
workspacesWithDeletions={workspacesWithImpendingDeletions?.workspaces}
|
||||
error={error}
|
||||
count={data?.count}
|
||||
page={pagination.page}
|
||||
|
|
|
@ -33,6 +33,7 @@ export const Language = {
|
|||
export interface WorkspacesPageViewProps {
|
||||
error: unknown
|
||||
workspaces?: Workspace[]
|
||||
workspacesWithDeletions?: Workspace[]
|
||||
count?: number
|
||||
filterProps: ComponentProps<typeof WorkspacesFilter>
|
||||
page: number
|
||||
|
@ -45,6 +46,7 @@ export const WorkspacesPageView: FC<
|
|||
React.PropsWithChildren<WorkspacesPageViewProps>
|
||||
> = ({
|
||||
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<
|
|||
</Maybe>
|
||||
{/* <ImpendingDeletionBanner/> determines its own visibility */}
|
||||
<ImpendingDeletionBanner
|
||||
workspace={workspaces?.find((workspace) => workspace.deleting_at)}
|
||||
workspace={workspacesWithDeletions?.find(
|
||||
(workspace) => workspace.deleting_at,
|
||||
)}
|
||||
shouldRedisplayBanner={isNewWorkspacesImpendingDeletion()}
|
||||
onDismiss={() =>
|
||||
saveLocal(
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue