Skip to content

Commit 3d96b94

Browse files
committed
Extract watching workspace out of xstate
1 parent 2388cae commit 3d96b94

File tree

4 files changed

+47
-95
lines changed

4 files changed

+47
-95
lines changed

site/src/api/queries/workspaces.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@ export const workspaceByOwnerAndNameKey = (owner: string, name: string) => [
1818
"settings",
1919
];
2020

21-
export const workspaceByOwnerAndName = (
22-
owner: string,
23-
name: string,
24-
): QueryOptions<Workspace> => {
21+
export const workspaceByOwnerAndName = (owner: string, name: string) => {
2522
return {
2623
queryKey: workspaceByOwnerAndNameKey(owner, name),
2724
queryFn: () =>

site/src/pages/WorkspacePage/Workspace.stories.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -747,10 +747,3 @@ function makeFailedBuildLogs(): ProvisionerJobLog[] {
747747
},
748748
];
749749
}
750-
751-
export const UnsupportedWorkspace: Story = {
752-
args: {
753-
...Running.args,
754-
templateWarnings: ["UNSUPPORTED_WORKSPACES"],
755-
},
756-
};

site/src/pages/WorkspacePage/WorkspacePage.tsx

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useMachine } from "@xstate/react";
22
import { Loader } from "components/Loader/Loader";
3-
import { FC } from "react";
3+
import { FC, useEffect, useRef } from "react";
44
import { useParams } from "react-router-dom";
55
import { workspaceMachine } from "xServices/workspace/workspaceXService";
66
import { WorkspaceReadyPage } from "./WorkspaceReadyPage";
@@ -9,14 +9,17 @@ import { ErrorAlert } from "components/Alert/ErrorAlert";
99
import { useOrganizationId } from "hooks";
1010
import { isAxiosError } from "axios";
1111
import { Margins } from "components/Margins/Margins";
12-
import { useInfiniteQuery, useQuery } from "react-query";
12+
import { useInfiniteQuery, useQuery, useQueryClient } from "react-query";
1313
import { infiniteWorkspaceBuilds } from "api/queries/workspaceBuilds";
1414
import { templateByName } from "api/queries/templates";
1515
import { workspaceByOwnerAndName } from "api/queries/workspaces";
1616
import { checkAuthorization } from "api/queries/authCheck";
1717
import { WorkspacePermissions, workspaceChecks } from "./permissions";
18+
import { watchWorkspace } from "api/api";
19+
import { Workspace } from "api/typesGenerated";
1820

1921
export const WorkspacePage: FC = () => {
22+
const queryClient = useQueryClient();
2023
const params = useParams() as {
2124
username: string;
2225
workspace: string;
@@ -37,9 +40,11 @@ export const WorkspacePage: FC = () => {
3740
},
3841
});
3942

40-
const workspaceQuery = useQuery(
41-
workspaceByOwnerAndName(username, workspaceName),
43+
const workspaceQueryOptions = workspaceByOwnerAndName(
44+
username,
45+
workspaceName,
4246
);
47+
const workspaceQuery = useQuery(workspaceQueryOptions);
4348
const workspace = workspaceQuery.data;
4449

4550
const templateQuery = useQuery({
@@ -65,6 +70,43 @@ export const WorkspacePage: FC = () => {
6570
workspaceQuery.error ?? templateQuery.error ?? permissionsQuery.error;
6671
const isLoading = !workspace || !template || !permissions;
6772

73+
// Watch workspace changes
74+
const workspaceEventSource = useRef<EventSource | null>(null);
75+
useEffect(() => {
76+
// If there is an event source, we are already watching the workspace
77+
if (!workspace || workspaceEventSource.current) {
78+
return;
79+
}
80+
81+
const eventSource = watchWorkspace(workspace.id);
82+
workspaceEventSource.current = eventSource;
83+
84+
eventSource.addEventListener("data", async (event) => {
85+
const newWorkspaceData = JSON.parse(event.data) as Workspace;
86+
queryClient.setQueryData(
87+
workspaceQueryOptions.queryKey,
88+
newWorkspaceData,
89+
);
90+
91+
const hasNewBuild =
92+
newWorkspaceData.latest_build.id !== workspace.latest_build.id;
93+
const lastBuildHasChanged =
94+
newWorkspaceData.latest_build.status !== workspace.latest_build.status;
95+
96+
if (hasNewBuild || lastBuildHasChanged) {
97+
await buildsQuery.refetch();
98+
}
99+
});
100+
101+
eventSource.addEventListener("error", (event) => {
102+
console.error("Error on getting workspace changes.", event);
103+
});
104+
105+
return () => {
106+
eventSource.close();
107+
};
108+
}, [buildsQuery, queryClient, workspace, workspaceQueryOptions.queryKey]);
109+
68110
if (pageError) {
69111
return (
70112
<Margins>

site/src/xServices/workspace/workspaceXService.ts

Lines changed: 0 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -104,35 +104,6 @@ export const workspaceMachine = createMachine(
104104
},
105105
},
106106
states: {
107-
listening: {
108-
initial: "gettingEvents",
109-
states: {
110-
gettingEvents: {
111-
entry: ["initializeEventSource"],
112-
exit: "closeEventSource",
113-
invoke: {
114-
src: "listening",
115-
id: "listening",
116-
},
117-
on: {
118-
REFRESH_WORKSPACE: {
119-
actions: ["refreshWorkspace"],
120-
},
121-
EVENT_SOURCE_ERROR: {
122-
target: "error",
123-
},
124-
},
125-
},
126-
error: {
127-
entry: "logWatchWorkspaceWarning",
128-
after: {
129-
"2000": {
130-
target: "gettingEvents",
131-
},
132-
},
133-
},
134-
},
135-
},
136107
build: {
137108
initial: "idle",
138109
states: {
@@ -359,20 +330,6 @@ export const workspaceMachine = createMachine(
359330
clearCancellationError: assign({
360331
cancellationError: (_) => undefined,
361332
}),
362-
// SSE related actions
363-
// open a new EventSource so we can stream SSE
364-
initializeEventSource: assign({
365-
eventSource: (context) =>
366-
context.workspace && API.watchWorkspace(context.workspace.id),
367-
}),
368-
closeEventSource: (context) =>
369-
context.eventSource && context.eventSource.close(),
370-
refreshWorkspace: assign({
371-
workspace: (_, event) => event.data,
372-
}),
373-
logWatchWorkspaceWarning: (_, event) => {
374-
console.error("Watch workspace error:", event);
375-
},
376333
displayActivateError: (_, { data }) => {
377334
const message = getErrorMessage(data, "Error activate workspace.");
378335
displayError(message);
@@ -502,44 +459,7 @@ export const workspaceMachine = createMachine(
502459
throw Error("Cannot activate workspace without workspace id");
503460
}
504461
},
505-
listening: (context) => (send) => {
506-
if (!context.eventSource) {
507-
send({ type: "EVENT_SOURCE_ERROR", error: "error initializing sse" });
508-
return;
509-
}
510462

511-
context.eventSource.addEventListener("data", (event) => {
512-
const newWorkspaceData = JSON.parse(event.data) as TypesGen.Workspace;
513-
// refresh our workspace with each SSE
514-
send({ type: "REFRESH_WORKSPACE", data: newWorkspaceData });
515-
516-
const currentWorkspace = context.workspace!;
517-
const hasNewBuild =
518-
newWorkspaceData.latest_build.id !==
519-
currentWorkspace.latest_build.id;
520-
const lastBuildHasChanged =
521-
newWorkspaceData.latest_build.status !==
522-
currentWorkspace.latest_build.status;
523-
524-
if (hasNewBuild || lastBuildHasChanged) {
525-
send({ type: "REFRESH_TIMELINE" });
526-
}
527-
});
528-
529-
// handle any error events returned by our sse
530-
context.eventSource.addEventListener("error", (event) => {
531-
send({ type: "EVENT_SOURCE_ERROR", error: event });
532-
});
533-
534-
// handle any sse implementation exceptions
535-
context.eventSource.onerror = () => {
536-
send({ type: "EVENT_SOURCE_ERROR", error: "sse error" });
537-
};
538-
539-
return () => {
540-
context.eventSource?.close();
541-
};
542-
},
543463
},
544464
},
545465
);

0 commit comments

Comments
 (0)