Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3c99474
Remove template deprecation warning
BrunoQuaresma Nov 9, 2023
878f995
Move deployment values fetching
BrunoQuaresma Nov 9, 2023
cdf8859
Remove unused code from template warning
BrunoQuaresma Nov 9, 2023
964c3d2
Extract initial data
BrunoQuaresma Nov 9, 2023
2cd9de1
Move quota fetching to be used close to where it is used
BrunoQuaresma Nov 9, 2023
4fd62bb
Fix test on Terminal because of workspace query changes
BrunoQuaresma Nov 9, 2023
168024d
Merge branch 'main' of https://github.com/coder/coder into bq/refacto…
BrunoQuaresma Nov 13, 2023
b6ee59a
Move can suto start to be on WorkspaceReadyPage
BrunoQuaresma Nov 13, 2023
2388cae
Extract ssh config
BrunoQuaresma Nov 13, 2023
3d96b94
Extract watching workspace out of xstate
BrunoQuaresma Nov 13, 2023
b0fba32
Remove unused code
BrunoQuaresma Nov 13, 2023
9edfd71
Organize code on ready page
BrunoQuaresma Nov 13, 2023
3e1de93
Extract change version from xstate
BrunoQuaresma Nov 13, 2023
ab8977c
Extract update workspace
BrunoQuaresma Nov 13, 2023
cb2c966
Extract delete
BrunoQuaresma Nov 13, 2023
6c8ec9f
Extract activate
BrunoQuaresma Nov 13, 2023
039f4bb
Remove unecessary require permission
BrunoQuaresma Nov 13, 2023
987f14f
Remove unused RequirePermission usage
BrunoQuaresma Nov 13, 2023
13e954f
Extract refresh timeline
BrunoQuaresma Nov 13, 2023
764f047
Remove REFRESH_WORKSPACE
BrunoQuaresma Nov 13, 2023
633c431
Extract stop
BrunoQuaresma Nov 13, 2023
20e073f
Extract start
BrunoQuaresma Nov 13, 2023
bfcc99d
Extract cancellation and remove xservices
BrunoQuaresma Nov 13, 2023
cf6896a
Clean up
BrunoQuaresma Nov 13, 2023
b6c9469
Add retry with debug
BrunoQuaresma Nov 13, 2023
a4a33f9
Remove XState
BrunoQuaresma Nov 13, 2023
e72abf5
Merge branch 'main' of https://github.com/coder/coder into bq/refacto…
BrunoQuaresma Nov 14, 2023
75d39a2
Clean up event source
BrunoQuaresma Nov 14, 2023
c3de809
Rename workspace mutations
BrunoQuaresma Nov 14, 2023
477b928
Invalidate workspace builds data on cancel
BrunoQuaresma Nov 14, 2023
d189c20
use useEffectEvent to avoid unecessary updates
BrunoQuaresma Nov 14, 2023
49b95a2
Merge branch 'main' of https://github.com/coder/coder into bq/refacto…
BrunoQuaresma Nov 14, 2023
6cc257a
Fix cancel action
BrunoQuaresma Nov 14, 2023
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
Prev Previous commit
Next Next commit
Extract watching workspace out of xstate
  • Loading branch information
BrunoQuaresma committed Nov 13, 2023
commit 3d96b94496a4e1ec7e9727ca2384c7461ca7efe4
5 changes: 1 addition & 4 deletions site/src/api/queries/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ export const workspaceByOwnerAndNameKey = (owner: string, name: string) => [
"settings",
];

export const workspaceByOwnerAndName = (
owner: string,
name: string,
): QueryOptions<Workspace> => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why the return type was removed? If nothing else, I feel that some kind of type hint can be worth it , just because it helps with auto-complete

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was because when using the queryKey value from the QueryOptions it was returning a type error because it can be undefined:

Example:

queryClient.setQueryData(
  workspaceQueryOptions.queryKey,
  newWorkspaceData,
);

Error:

Argument of type 'QueryKey | undefined' is not assignable to parameter of type 'QueryKey'

I could try to better type it but for this case, I just think it is too much. At some point, we may create a utility type to help us with typing the query results.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, RQ v5's new queryOptions function might also help with this

export const workspaceByOwnerAndName = (owner: string, name: string) => {
return {
queryKey: workspaceByOwnerAndNameKey(owner, name),
queryFn: () =>
Expand Down
7 changes: 0 additions & 7 deletions site/src/pages/WorkspacePage/Workspace.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -747,10 +747,3 @@ function makeFailedBuildLogs(): ProvisionerJobLog[] {
},
];
}

export const UnsupportedWorkspace: Story = {
args: {
...Running.args,
templateWarnings: ["UNSUPPORTED_WORKSPACES"],
},
};
50 changes: 46 additions & 4 deletions site/src/pages/WorkspacePage/WorkspacePage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useMachine } from "@xstate/react";
import { Loader } from "components/Loader/Loader";
import { FC } from "react";
import { FC, useEffect, useRef } from "react";
import { useParams } from "react-router-dom";
import { workspaceMachine } from "xServices/workspace/workspaceXService";
import { WorkspaceReadyPage } from "./WorkspaceReadyPage";
Expand All @@ -9,14 +9,17 @@ import { ErrorAlert } from "components/Alert/ErrorAlert";
import { useOrganizationId } from "hooks";
import { isAxiosError } from "axios";
import { Margins } from "components/Margins/Margins";
import { useInfiniteQuery, useQuery } from "react-query";
import { useInfiniteQuery, useQuery, useQueryClient } from "react-query";
import { infiniteWorkspaceBuilds } from "api/queries/workspaceBuilds";
import { templateByName } from "api/queries/templates";
import { workspaceByOwnerAndName } from "api/queries/workspaces";
import { checkAuthorization } from "api/queries/authCheck";
import { WorkspacePermissions, workspaceChecks } from "./permissions";
import { watchWorkspace } from "api/api";
import { Workspace } from "api/typesGenerated";

export const WorkspacePage: FC = () => {
const queryClient = useQueryClient();
const params = useParams() as {
username: string;
workspace: string;
Expand All @@ -37,9 +40,11 @@ export const WorkspacePage: FC = () => {
},
});

const workspaceQuery = useQuery(
workspaceByOwnerAndName(username, workspaceName),
const workspaceQueryOptions = workspaceByOwnerAndName(
username,
workspaceName,
);
const workspaceQuery = useQuery(workspaceQueryOptions);
const workspace = workspaceQuery.data;

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

// Watch workspace changes
const workspaceEventSource = useRef<EventSource | null>(null);
useEffect(() => {
// If there is an event source, we are already watching the workspace
if (!workspace || workspaceEventSource.current) {
return;
}

const eventSource = watchWorkspace(workspace.id);
workspaceEventSource.current = eventSource;

eventSource.addEventListener("data", async (event) => {
const newWorkspaceData = JSON.parse(event.data) as Workspace;
queryClient.setQueryData(
workspaceQueryOptions.queryKey,
newWorkspaceData,
);

const hasNewBuild =
newWorkspaceData.latest_build.id !== workspace.latest_build.id;
const lastBuildHasChanged =
newWorkspaceData.latest_build.status !== workspace.latest_build.status;

if (hasNewBuild || lastBuildHasChanged) {
await buildsQuery.refetch();
}
});

eventSource.addEventListener("error", (event) => {
console.error("Error on getting workspace changes.", event);
});

return () => {
eventSource.close();
};
}, [buildsQuery, queryClient, workspace, workspaceQueryOptions.queryKey]);

if (pageError) {
return (
<Margins>
Expand Down
80 changes: 0 additions & 80 deletions site/src/xServices/workspace/workspaceXService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,35 +104,6 @@ export const workspaceMachine = createMachine(
},
},
states: {
listening: {
initial: "gettingEvents",
states: {
gettingEvents: {
entry: ["initializeEventSource"],
exit: "closeEventSource",
invoke: {
src: "listening",
id: "listening",
},
on: {
REFRESH_WORKSPACE: {
actions: ["refreshWorkspace"],
},
EVENT_SOURCE_ERROR: {
target: "error",
},
},
},
error: {
entry: "logWatchWorkspaceWarning",
after: {
"2000": {
target: "gettingEvents",
},
},
},
},
},
build: {
initial: "idle",
states: {
Expand Down Expand Up @@ -359,20 +330,6 @@ export const workspaceMachine = createMachine(
clearCancellationError: assign({
cancellationError: (_) => undefined,
}),
// SSE related actions
// open a new EventSource so we can stream SSE
initializeEventSource: assign({
eventSource: (context) =>
context.workspace && API.watchWorkspace(context.workspace.id),
}),
closeEventSource: (context) =>
context.eventSource && context.eventSource.close(),
refreshWorkspace: assign({
workspace: (_, event) => event.data,
}),
logWatchWorkspaceWarning: (_, event) => {
console.error("Watch workspace error:", event);
},
displayActivateError: (_, { data }) => {
const message = getErrorMessage(data, "Error activate workspace.");
displayError(message);
Expand Down Expand Up @@ -502,44 +459,7 @@ export const workspaceMachine = createMachine(
throw Error("Cannot activate workspace without workspace id");
}
},
listening: (context) => (send) => {
if (!context.eventSource) {
send({ type: "EVENT_SOURCE_ERROR", error: "error initializing sse" });
return;
}

context.eventSource.addEventListener("data", (event) => {
const newWorkspaceData = JSON.parse(event.data) as TypesGen.Workspace;
// refresh our workspace with each SSE
send({ type: "REFRESH_WORKSPACE", data: newWorkspaceData });

const currentWorkspace = context.workspace!;
const hasNewBuild =
newWorkspaceData.latest_build.id !==
currentWorkspace.latest_build.id;
const lastBuildHasChanged =
newWorkspaceData.latest_build.status !==
currentWorkspace.latest_build.status;

if (hasNewBuild || lastBuildHasChanged) {
send({ type: "REFRESH_TIMELINE" });
}
});

// handle any error events returned by our sse
context.eventSource.addEventListener("error", (event) => {
send({ type: "EVENT_SOURCE_ERROR", error: event });
});

// handle any sse implementation exceptions
context.eventSource.onerror = () => {
send({ type: "EVENT_SOURCE_ERROR", error: "sse error" });
};

return () => {
context.eventSource?.close();
};
},
},
},
);