Skip to content

Commit e9758c7

Browse files
committed
Rely on context instead of finite state for status
1 parent aee54a0 commit e9758c7

File tree

4 files changed

+76
-176
lines changed

4 files changed

+76
-176
lines changed

site/src/components/WorkspaceStatusBar/WorkspaceStatusBar.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ const Language = {
2323
stopping: "Stopping",
2424
error: "Build Failed",
2525
loading: "Loading Status",
26+
deleting: "Deleting",
27+
deleted: "Deleted"
2628
}
2729

2830
export interface WorkspaceStatusBarProps {
@@ -78,12 +80,7 @@ export const WorkspaceStatusBar: React.FC<WorkspaceStatusBarProps> = ({
7880
<div className={styles.horizontal}>
7981
<Typography variant="h4">{workspace.name}</Typography>
8082
<Box className={styles.statusChip} role="status">
81-
{workspaceStatus === "started" && Language.started}
82-
{workspaceStatus === "starting" && Language.starting}
83-
{workspaceStatus === "stopped" && Language.stopped}
84-
{workspaceStatus === "stopping" && Language.stopping}
85-
{workspaceStatus === "error" && Language.error}
86-
{workspaceStatus === "loading" && Language.loading}
83+
{Language[workspaceStatus]}
8784
</Box>
8885
</div>
8986

site/src/pages/WorkspacePage/WorkspacePage.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useActor } from "@xstate/react"
1+
import { useActor, useSelector } from "@xstate/react"
22
import React, { useContext, useEffect } from "react"
33
import { useParams } from "react-router-dom"
44
import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary"
@@ -8,8 +8,9 @@ import { Stack } from "../../components/Stack/Stack"
88
import { Workspace } from "../../components/Workspace/Workspace"
99
import { firstOrItem } from "../../util/array"
1010
import { XServiceContext } from "../../xServices/StateContext"
11+
import { selectWorkspaceStatus } from "../../xServices/workspace/workspaceSelectors"
1112

12-
export type WorkspaceStatus = "started" | "starting" | "stopped" | "stopping" | "error" | "loading"
13+
export type WorkspaceStatus = "started" | "starting" | "stopped" | "stopping" | "error" | "loading" | "deleting" | "deleted"
1314

1415
export const WorkspacePage: React.FC = () => {
1516
const { workspace: workspaceQueryParam } = useParams()
@@ -19,17 +20,7 @@ export const WorkspacePage: React.FC = () => {
1920
const [workspaceState, workspaceSend] = useActor(xServices.workspaceXService)
2021
const { workspace, template, organization, getWorkspaceError, getTemplateError, getOrganizationError } =
2122
workspaceState.context
22-
const workspaceStatus: WorkspaceStatus = workspaceState.matches("ready.build.started")
23-
? "started"
24-
: workspaceState.matches("ready.build.stopped")
25-
? "stopped"
26-
: workspaceState.hasTag("starting")
27-
? "starting"
28-
: workspaceState.hasTag("stopping")
29-
? "stopping"
30-
: workspaceState.matches("ready.build.error")
31-
? "error"
32-
: "loading"
23+
const workspaceStatus = useSelector(xServices.workspaceXService, selectWorkspaceStatus)
3324

3425
/**
3526
* Get workspace, template, and organization on mount and whenever workspaceId changes.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { State } from "xstate"
2+
import { WorkspaceBuildTransition } from "../../api/types"
3+
import { WorkspaceStatus } from "../../pages/WorkspacePage/WorkspacePage"
4+
import { WorkspaceContext, WorkspaceEvent } from "./workspaceXService"
5+
6+
const inProgressToStatus: Record<WorkspaceBuildTransition, Partial<WorkspaceStatus>> = {
7+
"start": "starting",
8+
"stop": "stopping",
9+
"delete": "deleting"
10+
}
11+
12+
const succeededToStatus: Record<WorkspaceBuildTransition, Partial<WorkspaceStatus>> = {
13+
"start": "started",
14+
"stop": "stopped",
15+
"delete": "deleted"
16+
}
17+
18+
export const selectWorkspaceStatus = (state: State<WorkspaceContext, WorkspaceEvent>): WorkspaceStatus => {
19+
const transition = state.context.workspace?.latest_build.transition as WorkspaceBuildTransition
20+
const jobStatus = state.context.workspace?.latest_build.job.status
21+
switch (jobStatus) {
22+
case undefined: return "loading"
23+
case "succeeded": return succeededToStatus[transition]
24+
case "pending": return inProgressToStatus[transition]
25+
case "running": return inProgressToStatus[transition]
26+
case "canceled": return "error"
27+
case "canceling": return "error"
28+
case "failed": return "error"
29+
}
30+
}

site/src/xServices/workspace/workspaceXService.ts

Lines changed: 39 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { assign, createMachine } from "xstate"
22
import * as API from "../../api/api"
33
import * as TypesGen from "../../api/typesGenerated"
44

5-
interface WorkspaceContext {
5+
export interface WorkspaceContext {
66
workspace?: TypesGen.Workspace
77
template?: TypesGen.Template
88
organization?: TypesGen.Organization
@@ -19,13 +19,12 @@ interface WorkspaceContext {
1919
refreshTemplateError: Error | unknown
2020
}
2121

22-
type WorkspaceEvent =
22+
export type WorkspaceEvent =
2323
| { type: "GET_WORKSPACE"; workspaceId: string }
2424
| { type: "START" }
2525
| { type: "STOP" }
2626
| { type: "RETRY" }
2727
| { type: "UPDATE" }
28-
| { type: "REFRESH_WORKSPACE" }
2928

3029
export const workspaceMachine = createMachine(
3130
{
@@ -82,7 +81,7 @@ export const workspaceMachine = createMachine(
8281
ready: {
8382
type: "parallel",
8483
states: {
85-
// We poll the workspace consistently to know if it becomes outdated
84+
// We poll the workspace consistently to know if it becomes outdated and to update build status
8685
pollingWorkspace: {
8786
initial: "refreshingWorkspace",
8887
states: {
@@ -91,13 +90,13 @@ export const workspaceMachine = createMachine(
9190
invoke: {
9291
id: "refreshWorkspace",
9392
src: "refreshWorkspace",
94-
onDone: { actions: "assignWorkspace"},
93+
onDone: { target: "waiting", actions: "assignWorkspace"},
9594
onError: { target: "waiting", actions: "assignRefreshWorkspaceError" },
9695
},
9796
},
9897
waiting: {
9998
after: {
100-
5000: "refreshingWorkspace"
99+
1000: "refreshingWorkspace"
101100
}
102101
}
103102
}
@@ -140,157 +139,65 @@ export const workspaceMachine = createMachine(
140139
},
141140
},
142141
build: {
143-
initial: "dispatch",
144-
on: {
145-
UPDATE: "#workspaceState.ready.build.refreshingTemplate",
146-
},
142+
initial: "idle",
147143
states: {
148-
dispatch: {
149-
always: [
150-
{
151-
cond: "workspaceIsStarted",
152-
target: "started",
153-
},
154-
{
155-
cond: "workspaceIsStopped",
156-
target: "stopped",
157-
},
158-
{
159-
cond: "workspaceIsStarting",
160-
target: "buildingStart",
161-
},
162-
{
163-
cond: "workspaceIsStopping",
164-
target: "buildingStop",
165-
},
166-
{ target: "error" },
167-
],
168-
},
169-
started: {
170-
on: {
171-
STOP: "requestingStop",
172-
},
173-
tags: "buildReady",
174-
},
175-
stopped: {
144+
idle: {
176145
on: {
177146
START: "requestingStart",
147+
STOP: "requestingStop",
148+
RETRY: [
149+
{ cond: "triedToStart", target: "requestingStart" },
150+
{ target: "requestingStop" }
151+
],
152+
UPDATE: "refreshingTemplate",
178153
},
179-
tags: "buildReady",
180154
},
181155
requestingStart: {
156+
entry: "clearBuildError",
182157
invoke: {
183158
id: "startWorkspace",
184159
src: "startWorkspace",
185160
onDone: {
186-
target: "buildingStart",
187-
actions: ["assignBuild", "clearJobError"],
161+
target: "idle",
162+
actions: "assignBuild"
188163
},
189164
onError: {
190-
target: "error",
191-
actions: "assignJobError",
192-
},
193-
},
194-
tags: ["buildLoading", "starting"],
165+
target: "idle",
166+
actions: "assignBuildError"
167+
}
168+
}
195169
},
196170
requestingStop: {
171+
entry: "clearBuildError",
197172
invoke: {
198173
id: "stopWorkspace",
199174
src: "stopWorkspace",
200-
onDone: { target: "buildingStop", actions: ["assignBuild", "clearJobError"] },
201-
onError: {
202-
target: "error",
203-
actions: "assignJobError",
204-
},
205-
},
206-
tags: ["buildLoading", "stopping"],
207-
},
208-
buildingStart: {
209-
initial: "refreshingWorkspace",
210-
states: {
211-
refreshingWorkspace: {
212-
entry: "clearRefreshWorkspaceError",
213-
invoke: {
214-
id: "refreshWorkspace",
215-
src: "refreshWorkspace",
216-
onDone: [
217-
{
218-
cond: "jobSucceeded",
219-
target: "#workspaceState.ready.build.started",
220-
actions: ["clearBuildError", "assignWorkspace"],
221-
},
222-
{ cond: "jobPendingOrRunning", target: "waiting", actions: "assignWorkspace" },
223-
{
224-
target: "#workspaceState.ready.build.error",
225-
actions: ["assignWorkspace", "assignBuildError"],
226-
},
227-
],
228-
onError: { target: "waiting", actions: "assignRefreshWorkspaceError" },
229-
},
230-
},
231-
waiting: {
232-
after: {
233-
1000: "refreshingWorkspace",
234-
},
235-
},
236-
},
237-
tags: ["buildLoading", "starting"],
238-
},
239-
buildingStop: {
240-
initial: "refreshingWorkspace",
241-
states: {
242-
refreshingWorkspace: {
243-
entry: "clearRefreshWorkspaceError",
244-
invoke: {
245-
id: "refreshWorkspace",
246-
src: "refreshWorkspace",
247-
onDone: [
248-
{
249-
cond: "jobSucceeded",
250-
target: "#workspaceState.ready.build.stopped",
251-
actions: ["clearBuildError", "assignWorkspace"],
252-
},
253-
{ cond: "jobPendingOrRunning", target: "waiting", actions: "assignWorkspace" },
254-
{
255-
target: "#workspaceState.ready.build.error",
256-
actions: ["assignWorkspace", "assignBuildError"],
257-
},
258-
],
259-
onError: { target: "waiting", actions: "assignRefreshWorkspaceError" },
260-
},
261-
},
262-
waiting: {
263-
after: {
264-
1000: "refreshingWorkspace",
265-
},
175+
onDone: {
176+
target: "idle",
177+
actions: "assignBuild"
266178
},
267-
},
268-
tags: ["buildLoading", "stopping"],
179+
onError: {
180+
target: "idle",
181+
actions: "assignBuildError"
182+
}
183+
}
269184
},
270185
refreshingTemplate: {
271186
entry: "clearRefreshTemplateError",
272187
invoke: {
273188
id: "refreshTemplate",
274189
src: "getTemplate",
275-
onDone: { target: "#workspaceState.ready.build.requestingStart", actions: "assignTemplate" },
276-
onError: { target: "error", actions: "assignRefreshTemplateError" },
277-
},
278-
},
279-
error: {
280-
on: {
281-
RETRY: [
282-
{
283-
cond: "triedToStart",
284-
target: "requestingStart",
285-
},
286-
{
287-
// this could also be post-delete
288-
target: "requestingStop",
289-
},
290-
],
291-
},
190+
onDone: {
191+
target: "requestingStart",
192+
actions: "assignTemplate"
193+
},
194+
onError: {
195+
target: "idle",
196+
actions: "assignRefreshTemplateError"
197+
}
198+
}
292199
},
293-
},
200+
}
294201
},
295202
},
296203
},
@@ -336,14 +243,6 @@ export const workspaceMachine = createMachine(
336243
assign({
337244
build: event.data,
338245
}),
339-
assignJobError: (_, event) =>
340-
assign({
341-
jobError: event.data,
342-
}),
343-
clearJobError: (_) =>
344-
assign({
345-
jobError: undefined,
346-
}),
347246
assignBuildError: (_, event) =>
348247
assign({
349248
buildError: event.data,
@@ -370,24 +269,7 @@ export const workspaceMachine = createMachine(
370269
}),
371270
},
372271
guards: {
373-
workspaceIsStarted: (context) =>
374-
context.workspace?.latest_build.transition === "start" &&
375-
context.workspace.latest_build.job.status === "succeeded",
376-
workspaceIsStopped: (context) =>
377-
context.workspace?.latest_build.transition === "stop" &&
378-
context.workspace.latest_build.job.status === "succeeded",
379-
workspaceIsStarting: (context) =>
380-
context.workspace?.latest_build.transition === "start" &&
381-
["pending", "running"].includes(context.workspace.latest_build.job.status),
382-
workspaceIsStopping: (context) =>
383-
context.workspace?.latest_build.transition === "stop" &&
384-
["pending", "running"].includes(context.workspace.latest_build.job.status),
385272
triedToStart: (context) => context.workspace?.latest_build.transition === "start",
386-
jobSucceeded: (context) => context.workspace?.latest_build.job.status === "succeeded",
387-
jobPendingOrRunning: (context) => {
388-
const status = context.workspace?.latest_build.job.status
389-
return status === "pending" || status === "running"
390-
},
391273
},
392274
services: {
393275
getWorkspace: async (_, event) => {

0 commit comments

Comments
 (0)