Skip to content

Commit 164146c

Browse files
feat(site): Show update confirmation dialog (#7420)
1 parent 6d24f7c commit 164146c

File tree

8 files changed

+98
-27
lines changed

8 files changed

+98
-27
lines changed

site/src/components/Dialogs/Dialog.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const DialogActionButtons: React.FC<DialogActionButtonsProps> = ({
6565
{onConfirm && (
6666
<LoadingButton
6767
fullWidth
68+
data-testid="confirm-button"
6869
variant="contained"
6970
onClick={onConfirm}
7071
color={typeToColor(type)}

site/src/components/Resources/AgentRow.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,10 @@ export const AgentRow: FC<AgentRowProps> = ({
114114
useEffect(() => {
115115
// We only want to fetch logs when they are actually shown,
116116
// otherwise we can make a lot of requests that aren't necessary.
117-
if (showStartupLogs) {
117+
if (showStartupLogs && logsMachine.can("FETCH_STARTUP_LOGS")) {
118118
sendLogsEvent("FETCH_STARTUP_LOGS")
119119
}
120-
}, [sendLogsEvent, showStartupLogs])
120+
}, [logsMachine, sendLogsEvent, showStartupLogs])
121121
const logListRef = useRef<List>(null)
122122
const logListDivRef = useRef<HTMLDivElement>(null)
123123
const startupLogs = useMemo(() => {

site/src/components/WorkspaceActions/Buttons.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const UpdateButton: FC<PropsWithChildren<WorkspaceAction>> = ({
2121

2222
return (
2323
<Button
24+
data-testid="workspace-update-button"
2425
variant="outlined"
2526
startIcon={<CloudQueueIcon />}
2627
onClick={handleAction}

site/src/pages/WorkspaceBuildPage/WorkspaceBuildPage.test.tsx

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
import { screen } from "@testing-library/react"
22
import WS from "jest-websocket-mock"
3-
import {
4-
MockWorkspace,
5-
MockWorkspaceBuild,
6-
renderWithAuth,
7-
} from "../../testHelpers/renderHelpers"
3+
import { renderWithAuth } from "../../testHelpers/renderHelpers"
84
import { WorkspaceBuildPage } from "./WorkspaceBuildPage"
5+
import { MockWorkspace, MockWorkspaceBuild } from "testHelpers/entities"
96

107
describe("WorkspaceBuildPage", () => {
118
test("the mock server seamlessly handles JSON protocols", async () => {

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

+35-19
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ const { t } = i18next
3535
const renderWorkspacePage = async () => {
3636
jest.spyOn(api, "getTemplate").mockResolvedValueOnce(MockTemplate)
3737
jest.spyOn(api, "getTemplateVersionRichParameters").mockResolvedValueOnce([])
38+
jest.spyOn(api, "watchStartupLogs").mockImplementation((_, options) => {
39+
options.onDone()
40+
return new WebSocket("")
41+
})
3842
renderWithAuth(<WorkspacePage />, {
3943
route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}`,
4044
path: "/@:username/:workspace",
@@ -188,22 +192,32 @@ describe("WorkspacePage", () => {
188192
})
189193

190194
it("requests an update when the user presses Update", async () => {
195+
// Mocks
191196
jest
192197
.spyOn(api, "getWorkspaceByOwnerAndName")
193198
.mockResolvedValueOnce(MockOutdatedWorkspace)
199+
194200
const updateWorkspaceMock = jest
195201
.spyOn(api, "updateWorkspace")
196202
.mockResolvedValueOnce(MockWorkspaceBuild)
197203

198-
await testButton(
199-
t("actionButton.update", { ns: "workspacePage" }),
200-
updateWorkspaceMock,
201-
)
204+
// Render
205+
await renderWorkspacePage()
206+
207+
// Actions
208+
const user = userEvent.setup()
209+
await user.click(screen.getByTestId("workspace-update-button"))
210+
const confirmButton = await screen.findByTestId("confirm-button")
211+
await user.click(confirmButton)
212+
213+
// Assertions
214+
await waitFor(() => {
215+
expect(updateWorkspaceMock).toBeCalled()
216+
})
202217
})
203218

204219
it("updates the parameters when they are missing during update", async () => {
205-
// Setup mocks
206-
const user = userEvent.setup()
220+
// Mocks
207221
jest
208222
.spyOn(api, "getWorkspaceByOwnerAndName")
209223
.mockResolvedValueOnce(MockOutdatedWorkspace)
@@ -215,23 +229,24 @@ describe("WorkspacePage", () => {
215229
MockTemplateVersionParameter2,
216230
]),
217231
)
218-
// Render page and wait for it to be loaded
219-
renderWithAuth(<WorkspacePage />, {
220-
route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}`,
221-
path: "/@:username/:workspace",
222-
})
223-
await waitForLoaderToBeRemoved()
224-
// Click on the update button
225-
const workspaceActions = screen.getByTestId("workspace-actions")
226-
await user.click(
227-
within(workspaceActions).getByRole("button", { name: "Update" }),
228-
)
232+
233+
// Render
234+
await renderWorkspacePage()
235+
236+
// Actions
237+
const user = userEvent.setup()
238+
await user.click(screen.getByTestId("workspace-update-button"))
239+
const confirmButton = await screen.findByTestId("confirm-button")
240+
await user.click(confirmButton)
241+
242+
// The update was called
229243
await waitFor(() => {
230244
expect(api.updateWorkspace).toBeCalled()
231-
// We want to clear this mock to use it later
232245
updateWorkspaceSpy.mockClear()
233246
})
234-
// Fill the parameters and send the form
247+
248+
// After trying to update, a new dialog asking for missed parameters should
249+
// be displayed and filled
235250
const dialog = await screen.findByTestId("dialog")
236251
const firstParameterInput = within(dialog).getByLabelText(
237252
MockTemplateVersionParameter1.name,
@@ -246,6 +261,7 @@ describe("WorkspacePage", () => {
246261
await user.clear(secondParameterInput)
247262
await user.type(secondParameterInput, "2")
248263
await user.click(within(dialog).getByRole("button", { name: "Update" }))
264+
249265
// Check if the update was called using the values from the form
250266
await waitFor(() => {
251267
expect(api.updateWorkspace).toBeCalledWith(MockOutdatedWorkspace, [

site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx

+16-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ 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"
3435

3536
interface WorkspaceReadyPageProps {
3637
workspaceState: StateFrom<typeof workspaceMachine>
@@ -76,6 +77,7 @@ export const WorkspaceReadyPage = ({
7677
queryFn: () => getTemplateVersions(workspace.template_id),
7778
enabled: changeVersionDialogOpen,
7879
})
80+
const [isConfirmingUpdate, setIsConfirmingUpdate] = useState(false)
7981

8082
const {
8183
mutate: restartWorkspace,
@@ -132,7 +134,7 @@ export const WorkspaceReadyPage = ({
132134
handleStop={() => workspaceSend({ type: "STOP" })}
133135
handleRestart={() => restartWorkspace(workspace)}
134136
handleDelete={() => workspaceSend({ type: "ASK_DELETE" })}
135-
handleUpdate={() => workspaceSend({ type: "UPDATE" })}
137+
handleUpdate={() => setIsConfirmingUpdate(true)}
136138
handleCancel={() => workspaceSend({ type: "CANCEL" })}
137139
handleSettings={() => navigate("settings")}
138140
handleBuildRetry={() => workspaceSend({ type: "RETRY_BUILD" })}
@@ -198,6 +200,19 @@ export const WorkspaceReadyPage = ({
198200
})
199201
}}
200202
/>
203+
<ConfirmDialog
204+
type="info"
205+
hideCancel={false}
206+
open={isConfirmingUpdate}
207+
onConfirm={() => {
208+
workspaceSend({ type: "UPDATE" })
209+
setIsConfirmingUpdate(false)
210+
}}
211+
onClose={() => setIsConfirmingUpdate(false)}
212+
title="Confirm update"
213+
confirmText="Update"
214+
description="Are you sure you want to update your workspace? Updating your workspace will stop all running processes and delete non-persistent data."
215+
/>
201216
</>
202217
)
203218
}

site/src/testHelpers/entities.ts

+33
Original file line numberDiff line numberDiff line change
@@ -1639,3 +1639,36 @@ export const MockDeploymentStats: TypesGen.DeploymentStats = {
16391639
tx_bytes: 36113513253,
16401640
},
16411641
}
1642+
1643+
export const MockDeploymentSSH: TypesGen.SSHConfigResponse = {
1644+
hostname_prefix: " coder.",
1645+
ssh_config_options: {},
1646+
}
1647+
1648+
export const MockStartupLogs: TypesGen.WorkspaceAgentStartupLog[] = [
1649+
{
1650+
id: 166663,
1651+
created_at: "2023-05-04T11:30:41.402072Z",
1652+
output: "+ curl -fsSL https://code-server.dev/install.sh",
1653+
level: "info",
1654+
},
1655+
{
1656+
id: 166664,
1657+
created_at: "2023-05-04T11:30:41.40228Z",
1658+
output:
1659+
"+ sh -s -- --method=standalone --prefix=/tmp/code-server --version 4.8.3",
1660+
level: "info",
1661+
},
1662+
{
1663+
id: 166665,
1664+
created_at: "2023-05-04T11:30:42.590731Z",
1665+
output: "Ubuntu 22.04.2 LTS",
1666+
level: "info",
1667+
},
1668+
{
1669+
id: 166666,
1670+
created_at: "2023-05-04T11:30:42.593686Z",
1671+
output: "Installing v4.8.3 of the amd64 release from GitHub.",
1672+
level: "info",
1673+
},
1674+
]

site/src/testHelpers/handlers.ts

+8
Original file line numberDiff line numberDiff line change
@@ -385,4 +385,12 @@ export const handlers = [
385385
)
386386
},
387387
),
388+
389+
rest.get("/api/v2/deployment/ssh", (_, res, ctx) => {
390+
return res(ctx.status(200), ctx.json(M.MockDeploymentSSH))
391+
}),
392+
393+
rest.get("/api/v2/workspaceagents/:agent/startup-logs", (_, res, ctx) => {
394+
return res(ctx.status(200), ctx.json(M.MockStartupLogs))
395+
}),
388396
]

0 commit comments

Comments
 (0)