Skip to content

Commit e7bd049

Browse files
fix: Optimistically update the UI when a workspace action is triggered (#4929)
1 parent bf4a6fb commit e7bd049

File tree

6 files changed

+45
-24
lines changed

6 files changed

+45
-24
lines changed

site/src/components/DropdownButton/ActionCtas.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ const useStyles = makeStyles((theme) => ({
160160
// this is all custom to work with our button wrapper
161161
loadingButton: {
162162
border: "none",
163-
borderLeft: "1px solid #333740", // MUI disabled button
164-
borderRadius: "3px 0px 0px 3px",
163+
borderRadius: `${theme.shape.borderRadius} 0px 0px ${theme.shape.borderRadius}`,
165164
},
166165
}))

site/src/components/DropdownButton/DropdownButton.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ const useStyles = makeStyles((theme) => ({
9595
borderLeft: `1px solid ${theme.palette.divider}`,
9696
borderRadius: `0px ${theme.shape.borderRadius}px ${theme.shape.borderRadius}px 0px`,
9797
minWidth: "unset",
98-
width: "63px", // matching cancel button so button grouping doesn't grow in size
98+
width: "64px", // matching cancel button so button grouping doesn't grow in size
9999
"& .MuiButton-label": {
100100
marginRight: "8px",
101101
},

site/src/components/LoadingButton/LoadingButton.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const LoadingButton: FC<React.PropsWithChildren<LoadingButtonProps>> = ({
3131
<span style={hidden}>{children}</span>
3232
{loading && (
3333
<div className={styles.loader}>
34-
<CircularProgress size={18} className={styles.spinner} />
34+
<CircularProgress size={16} className={styles.spinner} />
3535
</div>
3636
)}
3737
{Boolean(loadingLabel) && loadingLabel}
@@ -63,7 +63,7 @@ const useStyles = makeStyles<Theme, StyleProps>((theme) => ({
6363
top: "50%",
6464
left: "50%",
6565
height: 22, // centering loading icon
66-
width: 18,
66+
width: 16,
6767
},
6868
spinner: {
6969
color: theme.palette.text.disabled,

site/src/components/WorkspaceActions/WorkspaceActions.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
6464
<DisabledButton label={t("disabledButton.deleted")} />
6565
),
6666
[ButtonTypesEnum.pending]: (
67-
<DisabledButton label={t("disabledButton.pending")} />
67+
<ActionLoadingButton label={t("disabledButton.pending")} />
6868
),
6969
}
7070

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

+13-11
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,6 @@ afterAll(() => {
9292
})
9393

9494
describe("WorkspacePage", () => {
95-
it("requests a stop job when the user presses Stop", async () => {
96-
const stopWorkspaceMock = jest
97-
.spyOn(api, "stopWorkspace")
98-
.mockResolvedValueOnce(MockWorkspaceBuild)
99-
testButton(
100-
t("actionButton.stop", { ns: "workspacePage" }),
101-
stopWorkspaceMock,
102-
)
103-
})
104-
10595
it("requests a delete job when the user presses Delete and confirms", async () => {
10696
const user = userEvent.setup()
10797
const deleteWorkspaceMock = jest
@@ -140,11 +130,23 @@ describe("WorkspacePage", () => {
140130
const startWorkspaceMock = jest
141131
.spyOn(api, "startWorkspace")
142132
.mockImplementation(() => Promise.resolve(MockWorkspaceBuild))
143-
testButton(
133+
await testButton(
144134
t("actionButton.start", { ns: "workspacePage" }),
145135
startWorkspaceMock,
146136
)
147137
})
138+
139+
it("requests a stop job when the user presses Stop", async () => {
140+
const stopWorkspaceMock = jest
141+
.spyOn(api, "stopWorkspace")
142+
.mockResolvedValueOnce(MockWorkspaceBuild)
143+
144+
await testButton(
145+
t("actionButton.stop", { ns: "workspacePage" }),
146+
stopWorkspaceMock,
147+
)
148+
})
149+
148150
it("requests cancellation when the user presses Cancel", async () => {
149151
server.use(
150152
rest.get(

site/src/xServices/workspace/workspaceXService.ts

+27-7
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ export const workspaceMachine = createMachine(
325325
},
326326
},
327327
requestingStart: {
328-
entry: "clearBuildError",
328+
entry: ["clearBuildError", "updateStatusToPending"],
329329
invoke: {
330330
src: "startWorkspace",
331331
id: "startWorkspace",
@@ -344,7 +344,7 @@ export const workspaceMachine = createMachine(
344344
},
345345
},
346346
requestingStop: {
347-
entry: "clearBuildError",
347+
entry: ["clearBuildError", "updateStatusToPending"],
348348
invoke: {
349349
src: "stopWorkspace",
350350
id: "stopWorkspace",
@@ -363,7 +363,7 @@ export const workspaceMachine = createMachine(
363363
},
364364
},
365365
requestingDelete: {
366-
entry: "clearBuildError",
366+
entry: ["clearBuildError", "updateStatusToPending"],
367367
invoke: {
368368
src: "deleteWorkspace",
369369
id: "deleteWorkspace",
@@ -382,7 +382,11 @@ export const workspaceMachine = createMachine(
382382
},
383383
},
384384
requestingCancel: {
385-
entry: ["clearCancellationMessage", "clearCancellationError"],
385+
entry: [
386+
"clearCancellationMessage",
387+
"clearCancellationError",
388+
"updateStatusToPending",
389+
],
386390
invoke: {
387391
src: "cancelWorkspace",
388392
id: "cancelWorkspace",
@@ -430,9 +434,7 @@ export const workspaceMachine = createMachine(
430434
on: {
431435
REFRESH_TIMELINE: {
432436
target: "#workspaceState.ready.timeline.gettingBuilds",
433-
cond: {
434-
type: "moreBuildsAvailable",
435-
},
437+
cond: "moreBuildsAvailable",
436438
},
437439
},
438440
},
@@ -599,6 +601,24 @@ export const workspaceMachine = createMachine(
599601
}),
600602
{ to: "scheduleBannerMachine" },
601603
),
604+
// Optimistically update. So when the user clicks on stop, we can show
605+
// the "pending" state right away without having to wait 0.5s ~ 2s to
606+
// display the visual feedback to the user.
607+
updateStatusToPending: assign({
608+
workspace: ({ workspace }) => {
609+
if (!workspace) {
610+
throw new Error("Workspace not defined")
611+
}
612+
613+
return {
614+
...workspace,
615+
latest_build: {
616+
...workspace.latest_build,
617+
status: "pending" as TypesGen.WorkspaceStatus,
618+
},
619+
}
620+
},
621+
}),
602622
},
603623
guards: {
604624
moreBuildsAvailable,

0 commit comments

Comments
 (0)