Skip to content

Commit 0c66523

Browse files
authored
feat: add impending deletion banner to workspace page (#7634)
* add banner to workspace page * fix prettier and lint * color-code banner * using warning instead * improve prop name for clarity
1 parent 5d2b2c5 commit 0c66523

File tree

7 files changed

+72
-48
lines changed

7 files changed

+72
-48
lines changed

site/src/components/Workspace/Workspace.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import {
2828
} from "components/PageHeader/FullWidthPageHeader"
2929
import { TemplateVersionWarnings } from "components/TemplateVersionWarnings/TemplateVersionWarnings"
3030
import { ErrorAlert } from "components/Alert/ErrorAlert"
31+
import { ImpendingDeletionBanner } from "components/WorkspaceDeletion"
32+
import { useLocalStorage } from "hooks"
33+
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
3134

3235
export enum WorkspaceErrors {
3336
GET_BUILDS_ERROR = "getBuildsError",
@@ -105,6 +108,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
105108
const navigate = useNavigate()
106109
const serverVersion = buildInfo?.version || ""
107110
const { t } = useTranslation("workspacePage")
111+
const { saveLocal, getLocal } = useLocalStorage()
108112

109113
const buildError = Boolean(workspaceErrors[WorkspaceErrors.BUILD_ERROR]) && (
110114
<ErrorAlert
@@ -183,10 +187,23 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
183187
{buildError}
184188
{cancellationError}
185189

186-
<WorkspaceDeletedBanner
187-
workspace={workspace}
188-
handleClick={() => navigate(`/templates`)}
189-
/>
190+
<ChooseOne>
191+
<Cond condition={workspace.latest_build.status === "deleted"}>
192+
<WorkspaceDeletedBanner
193+
handleClick={() => navigate(`/templates`)}
194+
/>
195+
</Cond>
196+
<Cond>
197+
{/* <ImpendingDeletionBanner/> determines its own visibility */}
198+
<ImpendingDeletionBanner
199+
workspace={workspace}
200+
shouldRedisplayBanner={
201+
getLocal("dismissedWorkspace") !== workspace.id
202+
}
203+
onDismiss={() => saveLocal("dismissedWorkspace", workspace.id)}
204+
/>
205+
</Cond>
206+
</ChooseOne>
190207

191208
<TemplateVersionWarnings warnings={templateWarnings} />
192209

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { action } from "@storybook/addon-actions"
22
import { Story } from "@storybook/react"
3-
import * as Mocks from "../../testHelpers/entities"
43
import {
54
WorkspaceDeletedBanner,
65
WorkspaceDeletedBannerProps,
@@ -18,16 +17,4 @@ const Template: Story<WorkspaceDeletedBannerProps> = (args) => (
1817
export const Example = Template.bind({})
1918
Example.args = {
2019
handleClick: action("extend"),
21-
workspace: {
22-
...Mocks.MockWorkspace,
23-
24-
latest_build: {
25-
...Mocks.MockWorkspaceBuild,
26-
job: {
27-
...Mocks.MockProvisionerJob,
28-
status: "succeeded",
29-
},
30-
transition: "delete",
31-
},
32-
},
3320
}
Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
import Button from "@mui/material/Button"
22
import { FC } from "react"
3-
import * as TypesGen from "api/typesGenerated"
43
import { Alert } from "components/Alert/Alert"
54
import { useTranslation } from "react-i18next"
6-
import { Maybe } from "components/Conditionals/Maybe"
75

86
export interface WorkspaceDeletedBannerProps {
9-
workspace: TypesGen.Workspace
107
handleClick: () => void
118
}
129

1310
export const WorkspaceDeletedBanner: FC<
1411
React.PropsWithChildren<WorkspaceDeletedBannerProps>
15-
> = ({ workspace, handleClick }) => {
12+
> = ({ handleClick }) => {
1613
const { t } = useTranslation("workspacePage")
1714

1815
const NewWorkspaceButton = (
@@ -22,10 +19,8 @@ export const WorkspaceDeletedBanner: FC<
2219
)
2320

2421
return (
25-
<Maybe condition={workspace.latest_build.status === "deleted"}>
26-
<Alert severity="warning" actions={[NewWorkspaceButton]}>
27-
{t("warningsAndErrors.workspaceDeletedWarning")}
28-
</Alert>
29-
</Maybe>
22+
<Alert severity="warning" actions={[NewWorkspaceButton]}>
23+
{t("warningsAndErrors.workspaceDeletedWarning")}
24+
</Alert>
3025
)
3126
}
Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
import { Workspace } from "api/typesGenerated"
22
import { displayImpendingDeletion } from "./utils"
33
import { useDashboard } from "components/Dashboard/DashboardProvider"
4-
import { Maybe } from "components/Conditionals/Maybe"
54
import { Alert } from "components/Alert/Alert"
5+
import { formatDistanceToNow, differenceInDays } from "date-fns"
6+
7+
export enum Count {
8+
Singular,
9+
Multiple,
10+
}
611

712
export const ImpendingDeletionBanner = ({
813
workspace,
914
onDismiss,
10-
displayImpendingDeletionBanner,
15+
shouldRedisplayBanner,
16+
count = Count.Singular,
1117
}: {
1218
workspace?: Workspace
1319
onDismiss: () => void
14-
displayImpendingDeletionBanner: boolean
20+
shouldRedisplayBanner: boolean
21+
count?: Count
1522
}): JSX.Element | null => {
1623
const { entitlements, experiments } = useDashboard()
1724
const allowAdvancedScheduling =
@@ -20,21 +27,36 @@ export const ImpendingDeletionBanner = ({
2027
// is merged up
2128
const allowWorkspaceActions = experiments.includes("workspace_actions")
2229

30+
if (
31+
!workspace ||
32+
!displayImpendingDeletion(
33+
workspace,
34+
allowAdvancedScheduling,
35+
allowWorkspaceActions,
36+
) ||
37+
// Banners should be redisplayed after dismissal when additional workspaces are newly scheduled for deletion
38+
!shouldRedisplayBanner
39+
) {
40+
return null
41+
}
42+
43+
// if deleting_at is 7 days away or less, display an 'error' banner to convey urgency to user
44+
const daysUntilDelete = differenceInDays(
45+
Date.parse(workspace.last_used_at),
46+
new Date(),
47+
)
48+
2349
return (
24-
<Maybe
25-
condition={Boolean(
26-
workspace &&
27-
displayImpendingDeletion(
28-
workspace,
29-
allowAdvancedScheduling,
30-
allowWorkspaceActions,
31-
) &&
32-
displayImpendingDeletionBanner,
33-
)}
50+
<Alert
51+
severity={daysUntilDelete <= 7 ? "warning" : "info"}
52+
onDismiss={onDismiss}
53+
dismissible
3454
>
35-
<Alert severity="info" onDismiss={onDismiss} dismissible>
36-
You have workspaces that will be deleted soon.
37-
</Alert>
38-
</Maybe>
55+
{count === Count.Singular
56+
? `This workspace has been unused for ${formatDistanceToNow(
57+
Date.parse(workspace.last_used_at),
58+
)} and is scheduled for deletion. To keep it, connect via SSH or the web terminal.`
59+
: "You have workspaces that will be deleted soon due to inactivity. To keep these workspaces, connect to them via SSH or the web terminal."}
60+
</Alert>
3961
)
4062
}

site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ const TemplateSchedulePage: FC = () => {
3232
{
3333
onSuccess: () => {
3434
displaySuccess("Template updated successfully")
35-
// clear browser-stored list of workspaces impending deletion
36-
clearLocal("dismissedWorkspaceList")
35+
// clear browser storage of workspaces impending deletion
36+
clearLocal("dismissedWorkspaceList") // workspaces page
37+
clearLocal("dismissedWorkspace") // workspace page
3738
},
3839
},
3940
)

site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe("WorkspacesPage", () => {
6161
renderWithAuth(<WorkspacesPage />)
6262

6363
const banner = await screen.findByText(
64-
"You have workspaces that will be deleted soon.",
64+
"You have workspaces that will be deleted soon due to inactivity. To keep these workspaces, connect to them via SSH or the web terminal.",
6565
)
6666
const user = userEvent.setup()
6767
await user.click(screen.getByTestId("dismiss-banner-btn"))

site/src/pages/WorkspacesPage/WorkspacesPageView.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { WorkspacesTable } from "components/WorkspacesTable/WorkspacesTable"
1717
import { workspaceFilterQuery } from "utils/filters"
1818
import { useLocalStorage } from "hooks"
1919
import difference from "lodash/difference"
20-
import { ImpendingDeletionBanner } from "components/WorkspaceDeletion"
20+
import { ImpendingDeletionBanner, Count } from "components/WorkspaceDeletion"
2121
import { ErrorAlert } from "components/Alert/ErrorAlert"
2222

2323
export const Language = {
@@ -117,15 +117,17 @@ export const WorkspacesPageView: FC<
117117
<Maybe condition={Boolean(error)}>
118118
<ErrorAlert error={error} />
119119
</Maybe>
120+
{/* <ImpendingDeletionBanner/> determines its own visibility */}
120121
<ImpendingDeletionBanner
121122
workspace={workspaces?.find((workspace) => workspace.deleting_at)}
122-
displayImpendingDeletionBanner={isNewWorkspacesImpendingDeletion()}
123+
shouldRedisplayBanner={isNewWorkspacesImpendingDeletion()}
123124
onDismiss={() =>
124125
saveLocal(
125126
"dismissedWorkspaceList",
126127
JSON.stringify(workspaceIdsWithImpendingDeletions),
127128
)
128129
}
130+
count={Count.Multiple}
129131
/>
130132

131133
<SearchBarWithFilter

0 commit comments

Comments
 (0)