Skip to content

Commit 11b7732

Browse files
feat(site): Show confirmation dialog on restart (#7531)
1 parent d9fc94f commit 11b7732

File tree

3 files changed

+158
-12
lines changed

3 files changed

+158
-12
lines changed

site/src/components/WorkspaceActions/Buttons.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ export const RestartButton: FC<PropsWithChildren<WorkspaceAction>> = ({
5959
const { t } = useTranslation("workspacePage")
6060

6161
return (
62-
<Button size="small" startIcon={<ReplayIcon />} onClick={handleAction}>
62+
<Button
63+
size="small"
64+
startIcon={<ReplayIcon />}
65+
onClick={handleAction}
66+
data-testid="workspace-restart-button"
67+
>
6368
{t("actionButton.restart")}
6469
</Button>
6570
)

site/src/pages/WorkspacePage/WorkspacePage.test.tsx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
MockDeletedWorkspace,
2121
MockBuilds,
2222
MockTemplateVersion3,
23+
MockUser,
2324
} from "testHelpers/entities"
2425
import * as api from "../../api/api"
2526
import { Workspace } from "../../api/typesGenerated"
@@ -161,10 +162,41 @@ describe("WorkspacePage", () => {
161162
.spyOn(api, "stopWorkspace")
162163
.mockResolvedValueOnce(MockWorkspaceBuild)
163164

164-
await testButton("Restart", stopWorkspaceMock)
165+
// Render
166+
await renderWorkspacePage()
167+
168+
// Actions
169+
const user = userEvent.setup()
170+
await user.click(screen.getByTestId("workspace-restart-button"))
171+
const confirmButton = await screen.findByTestId("confirm-button")
172+
await user.click(confirmButton)
173+
174+
// Assertions
175+
await waitFor(() => {
176+
expect(stopWorkspaceMock).toBeCalled()
177+
})
178+
})
179+
180+
it("requests a stop without confirmation when the user presses Restart", async () => {
181+
const stopWorkspaceMock = jest
182+
.spyOn(api, "stopWorkspace")
183+
.mockResolvedValueOnce(MockWorkspaceBuild)
184+
window.localStorage.setItem(
185+
`${MockUser.id}_ignoredWarnings`,
186+
JSON.stringify({ restart: new Date().toISOString() }),
187+
)
188+
189+
// Render
190+
await renderWorkspacePage()
165191

166-
const button = await screen.findByText("Restarting")
167-
expect(button).toBeInTheDocument()
192+
// Actions
193+
const user = userEvent.setup()
194+
await user.click(screen.getByTestId("workspace-restart-button"))
195+
196+
// Assertions
197+
await waitFor(() => {
198+
expect(stopWorkspaceMock).toBeCalled()
199+
})
168200
})
169201

170202
it("requests cancellation when the user presses Cancel", async () => {

site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx

Lines changed: 117 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ProvisionerJobLog } from "api/typesGenerated"
33
import { useDashboard } from "components/Dashboard/DashboardProvider"
44
import dayjs from "dayjs"
55
import { useFeatureVisibility } from "hooks/useFeatureVisibility"
6-
import { useEffect, useState } from "react"
6+
import { FC, useEffect, useState } from "react"
77
import { Helmet } from "react-helmet-async"
88
import { useTranslation } from "react-i18next"
99
import { useNavigate } from "react-router-dom"
@@ -31,7 +31,13 @@ import { ChangeVersionDialog } from "./ChangeVersionDialog"
3131
import { useQuery } from "@tanstack/react-query"
3232
import { getTemplateVersions } from "api/api"
3333
import { useRestartWorkspace } from "./hooks"
34-
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"
34+
import {
35+
ConfirmDialog,
36+
ConfirmDialogProps,
37+
} from "components/Dialogs/ConfirmDialog/ConfirmDialog"
38+
import { useMe } from "hooks/useMe"
39+
import Checkbox from "@mui/material/Checkbox"
40+
import FormControlLabel from "@mui/material/FormControlLabel"
3541

3642
interface WorkspaceReadyPageProps {
3743
workspaceState: StateFrom<typeof workspaceMachine>
@@ -79,6 +85,9 @@ export const WorkspaceReadyPage = ({
7985
enabled: changeVersionDialogOpen,
8086
})
8187
const [isConfirmingUpdate, setIsConfirmingUpdate] = useState(false)
88+
const [isConfirmingRestart, setIsConfirmingRestart] = useState(false)
89+
const user = useMe()
90+
const { isWarningIgnored, ignoreWarning } = useIgnoreWarnings(user.id)
8291

8392
const {
8493
mutate: restartWorkspace,
@@ -133,9 +142,21 @@ export const WorkspaceReadyPage = ({
133142
workspace={workspace}
134143
handleStart={() => workspaceSend({ type: "START" })}
135144
handleStop={() => workspaceSend({ type: "STOP" })}
136-
handleRestart={() => restartWorkspace(workspace)}
137145
handleDelete={() => workspaceSend({ type: "ASK_DELETE" })}
138-
handleUpdate={() => setIsConfirmingUpdate(true)}
146+
handleRestart={() => {
147+
if (isWarningIgnored("restart")) {
148+
restartWorkspace(workspace)
149+
} else {
150+
setIsConfirmingRestart(true)
151+
}
152+
}}
153+
handleUpdate={() => {
154+
if (isWarningIgnored("update")) {
155+
workspaceSend({ type: "UPDATE" })
156+
} else {
157+
setIsConfirmingUpdate(true)
158+
}
159+
}}
139160
handleCancel={() => workspaceSend({ type: "CANCEL" })}
140161
handleSettings={() => navigate("settings")}
141162
handleBuildRetry={() => workspaceSend({ type: "RETRY_BUILD" })}
@@ -202,11 +223,12 @@ export const WorkspaceReadyPage = ({
202223
})
203224
}}
204225
/>
205-
<ConfirmDialog
206-
type="info"
207-
hideCancel={false}
226+
<WarningDialog
208227
open={isConfirmingUpdate}
209-
onConfirm={() => {
228+
onConfirm={(shouldIgnore) => {
229+
if (shouldIgnore) {
230+
ignoreWarning("update")
231+
}
210232
workspaceSend({ type: "UPDATE" })
211233
setIsConfirmingUpdate(false)
212234
}}
@@ -215,6 +237,93 @@ export const WorkspaceReadyPage = ({
215237
confirmText="Update"
216238
description="Are you sure you want to update your workspace? Updating your workspace will stop all running processes and delete non-persistent data."
217239
/>
240+
241+
<WarningDialog
242+
open={isConfirmingRestart}
243+
onConfirm={(shouldIgnore) => {
244+
if (shouldIgnore) {
245+
ignoreWarning("restart")
246+
}
247+
restartWorkspace(workspace)
248+
setIsConfirmingRestart(false)
249+
}}
250+
onClose={() => setIsConfirmingRestart(false)}
251+
title="Confirm restart"
252+
confirmText="Restart"
253+
description="Are you sure you want to restart your workspace? Updating your workspace will stop all running processes and delete non-persistent data."
254+
/>
218255
</>
219256
)
220257
}
258+
259+
type IgnoredWarnings = Record<string, string>
260+
261+
const useIgnoreWarnings = (prefix: string) => {
262+
const ignoredWarningsJSON = localStorage.getItem(`${prefix}_ignoredWarnings`)
263+
let ignoredWarnings: IgnoredWarnings | undefined
264+
if (ignoredWarningsJSON) {
265+
ignoredWarnings = JSON.parse(ignoredWarningsJSON)
266+
}
267+
268+
const isWarningIgnored = (warningId: string) => {
269+
return Boolean(ignoredWarnings?.[warningId])
270+
}
271+
272+
const ignoreWarning = (warningId: string) => {
273+
if (!ignoredWarnings) {
274+
ignoredWarnings = {}
275+
}
276+
ignoredWarnings[warningId] = new Date().toISOString()
277+
localStorage.setItem(
278+
`${prefix}_ignoredWarnings`,
279+
JSON.stringify(ignoredWarnings),
280+
)
281+
}
282+
283+
return {
284+
isWarningIgnored,
285+
ignoreWarning,
286+
}
287+
}
288+
289+
const WarningDialog: FC<
290+
Pick<
291+
ConfirmDialogProps,
292+
"open" | "onClose" | "title" | "confirmText" | "description"
293+
> & { onConfirm: (shouldIgnore: boolean) => void }
294+
> = ({ open, onConfirm, onClose, title, confirmText, description }) => {
295+
const [shouldIgnore, setShouldIgnore] = useState(false)
296+
297+
return (
298+
<ConfirmDialog
299+
type="info"
300+
hideCancel={false}
301+
open={open}
302+
onConfirm={() => {
303+
onConfirm(shouldIgnore)
304+
}}
305+
onClose={onClose}
306+
title={title}
307+
confirmText={confirmText}
308+
description={
309+
<>
310+
<div>{description}</div>
311+
<FormControlLabel
312+
sx={{
313+
marginTop: 2,
314+
}}
315+
control={
316+
<Checkbox
317+
size="small"
318+
onChange={(e) => {
319+
setShouldIgnore(e.target.checked)
320+
}}
321+
/>
322+
}
323+
label="Don't show me this message again"
324+
/>
325+
</>
326+
}
327+
/>
328+
)
329+
}

0 commit comments

Comments
 (0)