Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions site/src/components/DropdownButton/ActionCtas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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...",
Expand Down
3 changes: 3 additions & 0 deletions site/src/components/Workspace/Workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface WorkspaceProps {
handleDelete: () => void
handleUpdate: () => void
handleCancel: () => void
isUpdating: boolean
workspace: TypesGen.Workspace
resources?: TypesGen.WorkspaceResource[]
builds?: TypesGen.WorkspaceBuild[]
Expand All @@ -61,6 +62,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
handleUpdate,
handleCancel,
workspace,
isUpdating,
resources,
builds,
canUpdateWorkspace,
Expand Down Expand Up @@ -104,6 +106,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
handleDelete={handleDelete}
handleUpdate={handleUpdate}
handleCancel={handleCancel}
isUpdating={isUpdating}
/>
</Stack>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const defaultArgs = {
handleDelete: action("delete"),
handleUpdate: action("update"),
handleCancel: action("cancel"),
isUpdating: false,
}

export const Starting = Template.bind({})
Expand Down Expand Up @@ -77,3 +78,10 @@ Errored.args = {
...defaultArgs,
workspace: Mocks.MockFailedWorkspace,
}

export const Updating = Template.bind({})
Updating.args = {
...defaultArgs,
isUpdating: true,
workspace: Mocks.MockOutdatedWorkspace,
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const renderComponent = async (props: Partial<WorkspaceActionsProps> = {}) => {
handleDelete={jest.fn()}
handleUpdate={jest.fn()}
handleCancel={jest.fn()}
isUpdating={false}
/>,
)
}
Expand All @@ -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")
Expand Down
7 changes: 6 additions & 1 deletion site/src/components/WorkspaceActions/WorkspaceActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface WorkspaceActionsProps {
handleDelete: () => void
handleUpdate: () => void
handleCancel: () => void
isUpdating: boolean
children?: ReactNode
}

Expand All @@ -37,6 +38,7 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
handleDelete,
handleUpdate,
handleCancel,
isUpdating,
}) => {
const workspaceStatus: keyof typeof WorkspaceStateEnum = getWorkspaceStatus(
workspace.latest_build,
Expand All @@ -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} />,
Expand All @@ -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) => ({
Expand Down
1 change: 1 addition & 0 deletions site/src/components/WorkspaceActions/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum ButtonTypesEnum {
delete = "delete",
deleting = "deleting",
update = "update",
updating = "updating",
// disabled buttons
canceling = "canceling",
disabled = "disabled",
Expand Down
27 changes: 26 additions & 1 deletion site/src/pages/WorkspacePage/WorkspacePage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions site/src/pages/WorkspacePage/WorkspacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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")}
Expand Down
43 changes: 31 additions & 12 deletions site/src/xServices/workspace/workspaceXService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export const workspaceMachine = createMachine(
START: "requestingStart",
STOP: "requestingStop",
ASK_DELETE: "askingDelete",
UPDATE: "requestingStartWithLatestTemplate",
UPDATE: "updatingWorkspace",
CANCEL: "requestingCancel",
},
},
Expand All @@ -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"],
},
},
},
},
},
Expand Down