mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: update workspace button should properly update the workspace (#4228)
* resolves #4098 * PR comments
This commit is contained in:
@@ -17,6 +17,7 @@ export const Language = {
|
||||
delete: "Delete",
|
||||
cancel: "Cancel",
|
||||
update: "Update",
|
||||
updating: "Updating",
|
||||
// these labels are used in WorkspaceActions.tsx
|
||||
starting: "Starting...",
|
||||
stopping: "Stopping...",
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface WorkspaceProps {
|
||||
handleDelete: () => void
|
||||
handleUpdate: () => void
|
||||
handleCancel: () => void
|
||||
isUpdating: boolean
|
||||
workspace: TypesGen.Workspace
|
||||
resources?: TypesGen.WorkspaceResource[]
|
||||
builds?: TypesGen.WorkspaceBuild[]
|
||||
@@ -61,6 +62,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
handleUpdate,
|
||||
handleCancel,
|
||||
workspace,
|
||||
isUpdating,
|
||||
resources,
|
||||
builds,
|
||||
canUpdateWorkspace,
|
||||
@@ -104,6 +106,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
||||
handleDelete={handleDelete}
|
||||
handleUpdate={handleUpdate}
|
||||
handleCancel={handleCancel}
|
||||
isUpdating={isUpdating}
|
||||
/>
|
||||
</Stack>
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ const defaultArgs = {
|
||||
handleDelete: action("delete"),
|
||||
handleUpdate: action("update"),
|
||||
handleCancel: action("cancel"),
|
||||
isUpdating: false,
|
||||
}
|
||||
|
||||
export const Starting = Template.bind({})
|
||||
@@ -77,3 +78,10 @@ Errored.args = {
|
||||
...defaultArgs,
|
||||
workspace: Mocks.MockFailedWorkspace,
|
||||
}
|
||||
|
||||
export const Updating = Template.bind({})
|
||||
Updating.args = {
|
||||
...defaultArgs,
|
||||
isUpdating: true,
|
||||
workspace: Mocks.MockOutdatedWorkspace,
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ const renderComponent = async (props: Partial<WorkspaceActionsProps> = {}) => {
|
||||
handleDelete={jest.fn()}
|
||||
handleUpdate={jest.fn()}
|
||||
handleCancel={jest.fn()}
|
||||
isUpdating={false}
|
||||
/>,
|
||||
)
|
||||
}
|
||||
@@ -27,6 +28,7 @@ const renderAndClick = async (props: Partial<WorkspaceActionsProps> = {}) => {
|
||||
handleDelete={jest.fn()}
|
||||
handleUpdate={jest.fn()}
|
||||
handleCancel={jest.fn()}
|
||||
isUpdating={false}
|
||||
/>,
|
||||
)
|
||||
const trigger = await screen.findByTestId("workspace-actions-button")
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface WorkspaceActionsProps {
|
||||
handleDelete: () => void
|
||||
handleUpdate: () => void
|
||||
handleCancel: () => void
|
||||
isUpdating: boolean
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
@@ -37,6 +38,7 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
handleDelete,
|
||||
handleUpdate,
|
||||
handleCancel,
|
||||
isUpdating,
|
||||
}) => {
|
||||
const workspaceStatus: keyof typeof WorkspaceStateEnum = getWorkspaceStatus(
|
||||
workspace.latest_build,
|
||||
@@ -63,6 +65,7 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
// A mapping of button type to the corresponding React component
|
||||
const buttonMapping: ButtonMapping = {
|
||||
[ButtonTypesEnum.update]: <UpdateButton handleAction={handleUpdate} />,
|
||||
[ButtonTypesEnum.updating]: <ActionLoadingButton label={Language.updating} />,
|
||||
[ButtonTypesEnum.start]: <StartButton handleAction={handleStart} />,
|
||||
[ButtonTypesEnum.starting]: <ActionLoadingButton label={Language.starting} />,
|
||||
[ButtonTypesEnum.stop]: <StopButton handleAction={handleStop} />,
|
||||
@@ -77,7 +80,9 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
|
||||
return (
|
||||
<DropdownButton
|
||||
primaryAction={buttonMapping[actions.primary]}
|
||||
primaryAction={
|
||||
isUpdating ? buttonMapping[ButtonTypesEnum.updating] : buttonMapping[actions.primary]
|
||||
}
|
||||
canCancel={actions.canCancel}
|
||||
handleCancel={handleCancel}
|
||||
secondaryActions={actions.secondary.map((action) => ({
|
||||
|
||||
@@ -10,6 +10,7 @@ export enum ButtonTypesEnum {
|
||||
delete = "delete",
|
||||
deleting = "deleting",
|
||||
update = "update",
|
||||
updating = "updating",
|
||||
// disabled buttons
|
||||
canceling = "canceling",
|
||||
disabled = "disabled",
|
||||
|
||||
@@ -157,7 +157,32 @@ describe("WorkspacePage", () => {
|
||||
return res(ctx.status(200), ctx.json(MockOutdatedWorkspace))
|
||||
}),
|
||||
)
|
||||
testButton(Language.update, getTemplateMock)
|
||||
|
||||
await renderWorkspacePage()
|
||||
const button = await screen.findByText(Language.update, { exact: true })
|
||||
fireEvent.click(button)
|
||||
|
||||
// getTemplate is called twice: once when the machine starts, and once after the user requests to update
|
||||
expect(getTemplateMock).toBeCalledTimes(2)
|
||||
})
|
||||
it("after an update postWorkspaceBuild is called with the latest template active version id", async () => {
|
||||
jest.spyOn(api, "getTemplate").mockResolvedValueOnce(MockTemplate) // active_version_id = "test-template-version"
|
||||
jest.spyOn(api, "startWorkspace").mockResolvedValueOnce({
|
||||
...MockWorkspaceBuild,
|
||||
})
|
||||
|
||||
server.use(
|
||||
rest.get(`/api/v2/users/:userId/workspace/:workspaceName`, (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(MockOutdatedWorkspace))
|
||||
}),
|
||||
)
|
||||
await renderWorkspacePage()
|
||||
const button = await screen.findByText(Language.update, { exact: true })
|
||||
fireEvent.click(button)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(api.startWorkspace).toBeCalledWith("test-workspace", "test-template-version"),
|
||||
)
|
||||
})
|
||||
it("shows the Stopping status when the workspace is stopping", async () => {
|
||||
await testStatus(MockStoppingWorkspace, DisplayStatusLanguage.stopping)
|
||||
|
||||
@@ -113,6 +113,7 @@ export const WorkspacePage: FC = () => {
|
||||
return canExtendDeadline(deadline, workspace, template)
|
||||
},
|
||||
}}
|
||||
isUpdating={workspaceState.hasTag("updating")}
|
||||
workspace={workspace}
|
||||
handleStart={() => workspaceSend("START")}
|
||||
handleStop={() => workspaceSend("STOP")}
|
||||
|
||||
@@ -257,7 +257,7 @@ export const workspaceMachine = createMachine(
|
||||
START: "requestingStart",
|
||||
STOP: "requestingStop",
|
||||
ASK_DELETE: "askingDelete",
|
||||
UPDATE: "requestingStartWithLatestTemplate",
|
||||
UPDATE: "updatingWorkspace",
|
||||
CANCEL: "requestingCancel",
|
||||
},
|
||||
},
|
||||
@@ -271,18 +271,37 @@ export const workspaceMachine = createMachine(
|
||||
},
|
||||
},
|
||||
},
|
||||
requestingStartWithLatestTemplate: {
|
||||
entry: "clearBuildError",
|
||||
invoke: {
|
||||
id: "startWorkspaceWithLatestTemplate",
|
||||
src: "startWorkspaceWithLatestTemplate",
|
||||
onDone: {
|
||||
target: "idle",
|
||||
actions: ["assignBuild"],
|
||||
updatingWorkspace: {
|
||||
tags: "updating",
|
||||
initial: "refreshingTemplate",
|
||||
states: {
|
||||
refreshingTemplate: {
|
||||
invoke: {
|
||||
id: "refreshTemplate",
|
||||
src: "getTemplate",
|
||||
onDone: {
|
||||
target: "startingWithLatestTemplate",
|
||||
actions: ["assignTemplate"],
|
||||
},
|
||||
onError: {
|
||||
target: "#workspaceState.ready.build.idle",
|
||||
actions: ["assignGetTemplateWarning"],
|
||||
},
|
||||
},
|
||||
},
|
||||
onError: {
|
||||
target: "idle",
|
||||
actions: ["assignBuildError"],
|
||||
startingWithLatestTemplate: {
|
||||
invoke: {
|
||||
id: "startWorkspaceWithLatestTemplate",
|
||||
src: "startWorkspaceWithLatestTemplate",
|
||||
onDone: {
|
||||
target: "#workspaceState.ready.build.idle",
|
||||
actions: ["assignBuild"],
|
||||
},
|
||||
onError: {
|
||||
target: "#workspaceState.ready.build.idle",
|
||||
actions: ["assignBuildError"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user