Skip to content

Commit 65ff604

Browse files
authored
fix: update workspace button should properly update the workspace (#4228)
* resolves #4098 * PR comments
1 parent fedb180 commit 65ff604

File tree

9 files changed

+79
-14
lines changed

9 files changed

+79
-14
lines changed

site/src/components/DropdownButton/ActionCtas.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const Language = {
1717
delete: "Delete",
1818
cancel: "Cancel",
1919
update: "Update",
20+
updating: "Updating",
2021
// these labels are used in WorkspaceActions.tsx
2122
starting: "Starting...",
2223
stopping: "Stopping...",

site/src/components/Workspace/Workspace.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export interface WorkspaceProps {
3939
handleDelete: () => void
4040
handleUpdate: () => void
4141
handleCancel: () => void
42+
isUpdating: boolean
4243
workspace: TypesGen.Workspace
4344
resources?: TypesGen.WorkspaceResource[]
4445
builds?: TypesGen.WorkspaceBuild[]
@@ -61,6 +62,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
6162
handleUpdate,
6263
handleCancel,
6364
workspace,
65+
isUpdating,
6466
resources,
6567
builds,
6668
canUpdateWorkspace,
@@ -104,6 +106,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
104106
handleDelete={handleDelete}
105107
handleUpdate={handleUpdate}
106108
handleCancel={handleCancel}
109+
isUpdating={isUpdating}
107110
/>
108111
</Stack>
109112
}

site/src/components/WorkspaceActions/WorkspaceActions.stories.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const defaultArgs = {
1616
handleDelete: action("delete"),
1717
handleUpdate: action("update"),
1818
handleCancel: action("cancel"),
19+
isUpdating: false,
1920
}
2021

2122
export const Starting = Template.bind({})
@@ -77,3 +78,10 @@ Errored.args = {
7778
...defaultArgs,
7879
workspace: Mocks.MockFailedWorkspace,
7980
}
81+
82+
export const Updating = Template.bind({})
83+
Updating.args = {
84+
...defaultArgs,
85+
isUpdating: true,
86+
workspace: Mocks.MockOutdatedWorkspace,
87+
}

site/src/components/WorkspaceActions/WorkspaceActions.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const renderComponent = async (props: Partial<WorkspaceActionsProps> = {}) => {
1414
handleDelete={jest.fn()}
1515
handleUpdate={jest.fn()}
1616
handleCancel={jest.fn()}
17+
isUpdating={false}
1718
/>,
1819
)
1920
}
@@ -27,6 +28,7 @@ const renderAndClick = async (props: Partial<WorkspaceActionsProps> = {}) => {
2728
handleDelete={jest.fn()}
2829
handleUpdate={jest.fn()}
2930
handleCancel={jest.fn()}
31+
isUpdating={false}
3032
/>,
3133
)
3234
const trigger = await screen.findByTestId("workspace-actions-button")

site/src/components/WorkspaceActions/WorkspaceActions.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface WorkspaceActionsProps {
2727
handleDelete: () => void
2828
handleUpdate: () => void
2929
handleCancel: () => void
30+
isUpdating: boolean
3031
children?: ReactNode
3132
}
3233

@@ -37,6 +38,7 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
3738
handleDelete,
3839
handleUpdate,
3940
handleCancel,
41+
isUpdating,
4042
}) => {
4143
const workspaceStatus: keyof typeof WorkspaceStateEnum = getWorkspaceStatus(
4244
workspace.latest_build,
@@ -63,6 +65,7 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
6365
// A mapping of button type to the corresponding React component
6466
const buttonMapping: ButtonMapping = {
6567
[ButtonTypesEnum.update]: <UpdateButton handleAction={handleUpdate} />,
68+
[ButtonTypesEnum.updating]: <ActionLoadingButton label={Language.updating} />,
6669
[ButtonTypesEnum.start]: <StartButton handleAction={handleStart} />,
6770
[ButtonTypesEnum.starting]: <ActionLoadingButton label={Language.starting} />,
6871
[ButtonTypesEnum.stop]: <StopButton handleAction={handleStop} />,
@@ -77,7 +80,9 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
7780

7881
return (
7982
<DropdownButton
80-
primaryAction={buttonMapping[actions.primary]}
83+
primaryAction={
84+
isUpdating ? buttonMapping[ButtonTypesEnum.updating] : buttonMapping[actions.primary]
85+
}
8186
canCancel={actions.canCancel}
8287
handleCancel={handleCancel}
8388
secondaryActions={actions.secondary.map((action) => ({

site/src/components/WorkspaceActions/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export enum ButtonTypesEnum {
1010
delete = "delete",
1111
deleting = "deleting",
1212
update = "update",
13+
updating = "updating",
1314
// disabled buttons
1415
canceling = "canceling",
1516
disabled = "disabled",

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,32 @@ describe("WorkspacePage", () => {
157157
return res(ctx.status(200), ctx.json(MockOutdatedWorkspace))
158158
}),
159159
)
160-
testButton(Language.update, getTemplateMock)
160+
161+
await renderWorkspacePage()
162+
const button = await screen.findByText(Language.update, { exact: true })
163+
fireEvent.click(button)
164+
165+
// getTemplate is called twice: once when the machine starts, and once after the user requests to update
166+
expect(getTemplateMock).toBeCalledTimes(2)
167+
})
168+
it("after an update postWorkspaceBuild is called with the latest template active version id", async () => {
169+
jest.spyOn(api, "getTemplate").mockResolvedValueOnce(MockTemplate) // active_version_id = "test-template-version"
170+
jest.spyOn(api, "startWorkspace").mockResolvedValueOnce({
171+
...MockWorkspaceBuild,
172+
})
173+
174+
server.use(
175+
rest.get(`/api/v2/users/:userId/workspace/:workspaceName`, (req, res, ctx) => {
176+
return res(ctx.status(200), ctx.json(MockOutdatedWorkspace))
177+
}),
178+
)
179+
await renderWorkspacePage()
180+
const button = await screen.findByText(Language.update, { exact: true })
181+
fireEvent.click(button)
182+
183+
await waitFor(() =>
184+
expect(api.startWorkspace).toBeCalledWith("test-workspace", "test-template-version"),
185+
)
161186
})
162187
it("shows the Stopping status when the workspace is stopping", async () => {
163188
await testStatus(MockStoppingWorkspace, DisplayStatusLanguage.stopping)

site/src/pages/WorkspacePage/WorkspacePage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export const WorkspacePage: FC = () => {
113113
return canExtendDeadline(deadline, workspace, template)
114114
},
115115
}}
116+
isUpdating={workspaceState.hasTag("updating")}
116117
workspace={workspace}
117118
handleStart={() => workspaceSend("START")}
118119
handleStop={() => workspaceSend("STOP")}

site/src/xServices/workspace/workspaceXService.ts

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ export const workspaceMachine = createMachine(
257257
START: "requestingStart",
258258
STOP: "requestingStop",
259259
ASK_DELETE: "askingDelete",
260-
UPDATE: "requestingStartWithLatestTemplate",
260+
UPDATE: "updatingWorkspace",
261261
CANCEL: "requestingCancel",
262262
},
263263
},
@@ -271,18 +271,37 @@ export const workspaceMachine = createMachine(
271271
},
272272
},
273273
},
274-
requestingStartWithLatestTemplate: {
275-
entry: "clearBuildError",
276-
invoke: {
277-
id: "startWorkspaceWithLatestTemplate",
278-
src: "startWorkspaceWithLatestTemplate",
279-
onDone: {
280-
target: "idle",
281-
actions: ["assignBuild"],
274+
updatingWorkspace: {
275+
tags: "updating",
276+
initial: "refreshingTemplate",
277+
states: {
278+
refreshingTemplate: {
279+
invoke: {
280+
id: "refreshTemplate",
281+
src: "getTemplate",
282+
onDone: {
283+
target: "startingWithLatestTemplate",
284+
actions: ["assignTemplate"],
285+
},
286+
onError: {
287+
target: "#workspaceState.ready.build.idle",
288+
actions: ["assignGetTemplateWarning"],
289+
},
290+
},
282291
},
283-
onError: {
284-
target: "idle",
285-
actions: ["assignBuildError"],
292+
startingWithLatestTemplate: {
293+
invoke: {
294+
id: "startWorkspaceWithLatestTemplate",
295+
src: "startWorkspaceWithLatestTemplate",
296+
onDone: {
297+
target: "#workspaceState.ready.build.idle",
298+
actions: ["assignBuild"],
299+
},
300+
onError: {
301+
target: "#workspaceState.ready.build.idle",
302+
actions: ["assignBuildError"],
303+
},
304+
},
286305
},
287306
},
288307
},

0 commit comments

Comments
 (0)