feat(site): Show update confirmation dialog (#7420)

This commit is contained in:
Bruno Quaresma 2023-05-04 15:40:41 -03:00 committed by GitHub
parent 6d24f7c894
commit 164146c462
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 98 additions and 27 deletions

View File

@ -65,6 +65,7 @@ export const DialogActionButtons: React.FC<DialogActionButtonsProps> = ({
{onConfirm && (
<LoadingButton
fullWidth
data-testid="confirm-button"
variant="contained"
onClick={onConfirm}
color={typeToColor(type)}

View File

@ -114,10 +114,10 @@ export const AgentRow: FC<AgentRowProps> = ({
useEffect(() => {
// We only want to fetch logs when they are actually shown,
// otherwise we can make a lot of requests that aren't necessary.
if (showStartupLogs) {
if (showStartupLogs && logsMachine.can("FETCH_STARTUP_LOGS")) {
sendLogsEvent("FETCH_STARTUP_LOGS")
}
}, [sendLogsEvent, showStartupLogs])
}, [logsMachine, sendLogsEvent, showStartupLogs])
const logListRef = useRef<List>(null)
const logListDivRef = useRef<HTMLDivElement>(null)
const startupLogs = useMemo(() => {

View File

@ -21,6 +21,7 @@ export const UpdateButton: FC<PropsWithChildren<WorkspaceAction>> = ({
return (
<Button
data-testid="workspace-update-button"
variant="outlined"
startIcon={<CloudQueueIcon />}
onClick={handleAction}

View File

@ -1,11 +1,8 @@
import { screen } from "@testing-library/react"
import WS from "jest-websocket-mock"
import {
MockWorkspace,
MockWorkspaceBuild,
renderWithAuth,
} from "../../testHelpers/renderHelpers"
import { renderWithAuth } from "../../testHelpers/renderHelpers"
import { WorkspaceBuildPage } from "./WorkspaceBuildPage"
import { MockWorkspace, MockWorkspaceBuild } from "testHelpers/entities"
describe("WorkspaceBuildPage", () => {
test("the mock server seamlessly handles JSON protocols", async () => {

View File

@ -35,6 +35,10 @@ const { t } = i18next
const renderWorkspacePage = async () => {
jest.spyOn(api, "getTemplate").mockResolvedValueOnce(MockTemplate)
jest.spyOn(api, "getTemplateVersionRichParameters").mockResolvedValueOnce([])
jest.spyOn(api, "watchStartupLogs").mockImplementation((_, options) => {
options.onDone()
return new WebSocket("")
})
renderWithAuth(<WorkspacePage />, {
route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}`,
path: "/@:username/:workspace",
@ -188,22 +192,32 @@ describe("WorkspacePage", () => {
})
it("requests an update when the user presses Update", async () => {
// Mocks
jest
.spyOn(api, "getWorkspaceByOwnerAndName")
.mockResolvedValueOnce(MockOutdatedWorkspace)
const updateWorkspaceMock = jest
.spyOn(api, "updateWorkspace")
.mockResolvedValueOnce(MockWorkspaceBuild)
await testButton(
t("actionButton.update", { ns: "workspacePage" }),
updateWorkspaceMock,
)
// Render
await renderWorkspacePage()
// Actions
const user = userEvent.setup()
await user.click(screen.getByTestId("workspace-update-button"))
const confirmButton = await screen.findByTestId("confirm-button")
await user.click(confirmButton)
// Assertions
await waitFor(() => {
expect(updateWorkspaceMock).toBeCalled()
})
})
it("updates the parameters when they are missing during update", async () => {
// Setup mocks
const user = userEvent.setup()
// Mocks
jest
.spyOn(api, "getWorkspaceByOwnerAndName")
.mockResolvedValueOnce(MockOutdatedWorkspace)
@ -215,23 +229,24 @@ describe("WorkspacePage", () => {
MockTemplateVersionParameter2,
]),
)
// Render page and wait for it to be loaded
renderWithAuth(<WorkspacePage />, {
route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}`,
path: "/@:username/:workspace",
})
await waitForLoaderToBeRemoved()
// Click on the update button
const workspaceActions = screen.getByTestId("workspace-actions")
await user.click(
within(workspaceActions).getByRole("button", { name: "Update" }),
)
// Render
await renderWorkspacePage()
// Actions
const user = userEvent.setup()
await user.click(screen.getByTestId("workspace-update-button"))
const confirmButton = await screen.findByTestId("confirm-button")
await user.click(confirmButton)
// The update was called
await waitFor(() => {
expect(api.updateWorkspace).toBeCalled()
// We want to clear this mock to use it later
updateWorkspaceSpy.mockClear()
})
// Fill the parameters and send the form
// After trying to update, a new dialog asking for missed parameters should
// be displayed and filled
const dialog = await screen.findByTestId("dialog")
const firstParameterInput = within(dialog).getByLabelText(
MockTemplateVersionParameter1.name,
@ -246,6 +261,7 @@ describe("WorkspacePage", () => {
await user.clear(secondParameterInput)
await user.type(secondParameterInput, "2")
await user.click(within(dialog).getByRole("button", { name: "Update" }))
// Check if the update was called using the values from the form
await waitFor(() => {
expect(api.updateWorkspace).toBeCalledWith(MockOutdatedWorkspace, [

View File

@ -31,6 +31,7 @@ import { ChangeVersionDialog } from "./ChangeVersionDialog"
import { useQuery } from "@tanstack/react-query"
import { getTemplateVersions } from "api/api"
import { useRestartWorkspace } from "./hooks"
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"
interface WorkspaceReadyPageProps {
workspaceState: StateFrom<typeof workspaceMachine>
@ -76,6 +77,7 @@ export const WorkspaceReadyPage = ({
queryFn: () => getTemplateVersions(workspace.template_id),
enabled: changeVersionDialogOpen,
})
const [isConfirmingUpdate, setIsConfirmingUpdate] = useState(false)
const {
mutate: restartWorkspace,
@ -132,7 +134,7 @@ export const WorkspaceReadyPage = ({
handleStop={() => workspaceSend({ type: "STOP" })}
handleRestart={() => restartWorkspace(workspace)}
handleDelete={() => workspaceSend({ type: "ASK_DELETE" })}
handleUpdate={() => workspaceSend({ type: "UPDATE" })}
handleUpdate={() => setIsConfirmingUpdate(true)}
handleCancel={() => workspaceSend({ type: "CANCEL" })}
handleSettings={() => navigate("settings")}
handleBuildRetry={() => workspaceSend({ type: "RETRY_BUILD" })}
@ -198,6 +200,19 @@ export const WorkspaceReadyPage = ({
})
}}
/>
<ConfirmDialog
type="info"
hideCancel={false}
open={isConfirmingUpdate}
onConfirm={() => {
workspaceSend({ type: "UPDATE" })
setIsConfirmingUpdate(false)
}}
onClose={() => setIsConfirmingUpdate(false)}
title="Confirm update"
confirmText="Update"
description="Are you sure you want to update your workspace? Updating your workspace will stop all running processes and delete non-persistent data."
/>
</>
)
}

View File

@ -1639,3 +1639,36 @@ export const MockDeploymentStats: TypesGen.DeploymentStats = {
tx_bytes: 36113513253,
},
}
export const MockDeploymentSSH: TypesGen.SSHConfigResponse = {
hostname_prefix: " coder.",
ssh_config_options: {},
}
export const MockStartupLogs: TypesGen.WorkspaceAgentStartupLog[] = [
{
id: 166663,
created_at: "2023-05-04T11:30:41.402072Z",
output: "+ curl -fsSL https://code-server.dev/install.sh",
level: "info",
},
{
id: 166664,
created_at: "2023-05-04T11:30:41.40228Z",
output:
"+ sh -s -- --method=standalone --prefix=/tmp/code-server --version 4.8.3",
level: "info",
},
{
id: 166665,
created_at: "2023-05-04T11:30:42.590731Z",
output: "Ubuntu 22.04.2 LTS",
level: "info",
},
{
id: 166666,
created_at: "2023-05-04T11:30:42.593686Z",
output: "Installing v4.8.3 of the amd64 release from GitHub.",
level: "info",
},
]

View File

@ -385,4 +385,12 @@ export const handlers = [
)
},
),
rest.get("/api/v2/deployment/ssh", (_, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockDeploymentSSH))
}),
rest.get("/api/v2/workspaceagents/:agent/startup-logs", (_, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockStartupLogs))
}),
]