From a813f7ffb24da15989699dd753cb1353465e2ad3 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Mon, 19 Sep 2022 18:39:59 +0000 Subject: [PATCH 1/6] added error handling --- .../src/pages/WorkspacePage/WorkspacePage.tsx | 38 +++ .../xServices/workspace/workspaceXService.ts | 290 +++++++++++------- site/webpack.dev.ts | 2 + 3 files changed, 219 insertions(+), 111 deletions(-) diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index eaf9ce1117efb..387a5aeda449d 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -51,6 +51,44 @@ export const WorkspacePage: FC = () => { cancellationError, } = workspaceState.context + useEffect(() => { + // console.log("workspace updating in component", workspace) + // console.log("resources", resources) + }, [workspace, resources]) + + // useEffect(() => { + // if (!workspace?.id) { + // return + // } + // + // console.log("hey im in here") + // const sse = new EventSource( + // `${location.protocol}//${location.host}/api/v2/workspaces/${workspace.id}/watch`, + // { withCredentials: true }, + // ) + // + // sse.onmessage = (e) => console.log("event", e) + // + // sse.addEventListener("ping", (event) => { + // console.log("ping event", event) + // }) + // + // sse.addEventListener("data", (event) => { + // console.log("data event", event) + // }) + // + // sse.onerror = (error) => { + // // error log here + // console.log("error", error) + // + // sse.close() + // } + // + // return () => { + // sse.close() + // } + // }, [workspace]) + const canUpdateWorkspace = Boolean(permissions?.updateWorkspace) const [bannerState, bannerSend] = useMachine(workspaceScheduleBannerMachine) diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index 94c1a02c220b0..9a94eb496d9eb 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -5,12 +5,12 @@ import * as Types from "../../api/types" import * as TypesGen from "../../api/typesGenerated" import { displayError, displaySuccess } from "../../components/GlobalSnackbar/utils" -const latestBuild = (builds: TypesGen.WorkspaceBuild[]) => { - // Cloning builds to not change the origin object with the sort() - return [...builds].sort((a, b) => { - return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime() - })[0] -} +// const latestBuild = (builds: TypesGen.WorkspaceBuild[]) => { +// // Cloning builds to not change the origin object with the sort() +// return [...builds].sort((a, b) => { +// return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime() +// })[0] +// } const Language = { refreshTemplateError: "Error updating workspace: latest template could not be fetched.", @@ -20,8 +20,8 @@ const Language = { type Permissions = Record, boolean> export interface WorkspaceContext { - workspace?: TypesGen.Workspace - template?: TypesGen.Template + workspace?: TypesGen.Workspace | TypesGen.ServerSentEvent["data"] + template?: TypesGen.Template | TypesGen.ServerSentEvent["data"] build?: TypesGen.WorkspaceBuild resources?: TypesGen.WorkspaceResource[] getWorkspaceError?: Error | unknown @@ -38,7 +38,7 @@ export interface WorkspaceContext { cancellationMessage?: Types.Message cancellationError?: Error | unknown // permissions - permissions?: Permissions + permissions?: Permissions | TypesGen.ServerSentEvent["data"] checkPermissionsError?: Error | unknown userId?: string } @@ -54,6 +54,8 @@ export type WorkspaceEvent = | { type: "CANCEL" } | { type: "LOAD_MORE_BUILDS" } | { type: "REFRESH_TIMELINE" } + | { type: "UPDATE_EVENT", data: TypesGen.ServerSentEvent["data"] } + | { type: "SSE_ERROR", error: Error | unknown } export const checks = { readWorkspace: "readWorkspace", @@ -196,40 +198,91 @@ export const workspaceMachine = createMachine( ], }, }, + sseFailure: { + entry: ["updateGetWorkspaceError", "updateGetResourcesError"], + after: { + "1000": { + target: 'ready.watchEvents' + } + } + }, ready: { type: "parallel", states: { - pollingWorkspace: { - initial: "refreshingWorkspace", - states: { - refreshingWorkspace: { - entry: "clearRefreshWorkspaceError", - invoke: { - src: "refreshWorkspace", - id: "refreshWorkspace", - onDone: [ - { - actions: ["refreshTimeline", "assignWorkspace"], - target: "waiting", - }, - ], - onError: [ - { - actions: "assignRefreshWorkspaceError", - target: "waiting", - }, - ], - }, + watchEvents: { + context: { + test: '' + }, + on: { + UPDATE_EVENT: { + actions: ["updateWorkspace", "updateResources"] }, - waiting: { - after: { - "1000": { - target: "refreshingWorkspace", - }, - }, + SSE_ERROR: { + target: "#workspaceState.sseFailure" + } + }, + entry: ["clearGetWorkspaceError", "clearGetResourcesError"], + invoke: { + src: (context) => (callback) => { + if (!context.workspace.id) { + callback({ type: "SSE_ERROR", error: "Cannot refresh workspace without id" }) + } + + // open a new EventSource so we can stream SSE + const sse = new EventSource(`${location.protocol}//${location.host}/api/v2/workspaces/${context.workspace?.id}/watch`, + { withCredentials: true } + ) + + // update our data objects (workspace, resources) with each SSE that comes back from the server + sse.addEventListener("data", (event) => { + callback({ type: "UPDATE_EVENT", data: JSON.parse(event.data) }) + }) + + // handle any error events returned by our sse + sse.addEventListener("error", (event) => { + callback({ type: "SSE_ERROR", error: event }) + }) + + // handle any sse implementation exceptions + sse.onerror = () => { + sse.close(); + callback({ type: "SSE_ERROR", error: "sse error" }) + } + }, }, }, + // pollingWorkspace: { + // initial: "refreshingWorkspace", + // states: { + // refreshingWorkspace: { + // entry: "clearRefreshWorkspaceError", + // invoke: { + // src: "refreshWorkspace", + // id: "refreshWorkspace", + // onDone: [ + // { + // actions: ["refreshTimeline", "assignWorkspace"], + // target: "waiting", + // }, + // ], + // onError: [ + // { + // actions: "assignRefreshWorkspaceError", + // target: "waiting", + // }, + // ], + // }, + // }, + // waiting: { + // after: { + // "1000": { + // target: "refreshingWorkspace", + // }, + // }, + // }, + // }, + // }, build: { initial: "idle", states: { @@ -368,37 +421,37 @@ export const workspaceMachine = createMachine( }, }, }, - pollingResources: { - initial: "gettingResources", - states: { - gettingResources: { - entry: "clearGetResourcesError", - invoke: { - src: "getResources", - id: "getResources", - onDone: [ - { - actions: "assignResources", - target: "waiting", - }, - ], - onError: [ - { - actions: "assignGetResourcesError", - target: "waiting", - }, - ], - }, - }, - waiting: { - after: { - "5000": { - target: "gettingResources", - }, - }, - }, - }, - }, + // pollingResources: { + // initial: "gettingResources", + // states: { + // gettingResources: { + // entry: "clearGetResourcesError", + // invoke: { + // src: "getResources", + // id: "getResources", + // onDone: [ + // { + // actions: "assignResources", + // target: "waiting", + // }, + // ], + // onError: [ + // { + // actions: "assignGetResourcesError", + // target: "waiting", + // }, + // ], + // }, + // }, + // waiting: { + // after: { + // "5000": { + // target: "gettingResources", + // }, + // }, + // }, + // }, + // }, timeline: { initial: "gettingBuilds", states: { @@ -478,6 +531,18 @@ export const workspaceMachine = createMachine( build: undefined, permissions: undefined, }), + updateWorkspace: assign({ + workspace: (_, event) => event.data, + }), + updateGetWorkspaceError: assign({ + getWorkspaceError: (_, event) => event, + }), + updateResources: assign({ + resources: (_, event) => event.data.latest_build.resources, + }), + updateGetResourcesError: assign({ + getResourcesError: (_, event) => event, + }), assignWorkspace: assign({ workspace: (_, event) => event.data, }), @@ -525,12 +590,12 @@ export const workspaceMachine = createMachine( clearCancellationError: assign({ cancellationError: (_) => undefined, }), - assignRefreshWorkspaceError: assign({ - refreshWorkspaceError: (_, event) => event.data, - }), - clearRefreshWorkspaceError: assign({ - refreshWorkspaceError: (_) => undefined, - }), + // assignRefreshWorkspaceError: assign({ + // refreshWorkspaceError: (_, event) => event.data, + // }), + // clearRefreshWorkspaceError: assign({ + // refreshWorkspaceError: (_) => undefined, + // }), assignRefreshTemplateError: assign({ refreshTemplateError: (_, event) => event.data, }), @@ -541,12 +606,12 @@ export const workspaceMachine = createMachine( refreshTemplateError: (_) => undefined, }), // Resources - assignResources: assign({ - resources: (_, event) => event.data, - }), - assignGetResourcesError: assign({ - getResourcesError: (_, event) => event.data, - }), + // assignResources: assign({ + // resources: (_, event) => event.data, + // }), + // assignGetResourcesError: assign({ + // getResourcesError: (_, event) => event.data, + // }), clearGetResourcesError: assign({ getResourcesError: (_) => undefined, }), @@ -578,22 +643,25 @@ export const workspaceMachine = createMachine( clearLoadMoreBuildsError: assign({ loadMoreBuildsError: (_) => undefined, }), - refreshTimeline: pure((context, event) => { + refreshTimeline: pure((context) => { // No need to refresh the timeline if it is not loaded if (!context.builds) { return } + + // KIRA REVISIT + return send({ type: "REFRESH_TIMELINE" }) // When it is a refresh workspace event, we want to check if the latest // build was updated to not over fetch the builds - if (event.type === "done.invoke.refreshWorkspace") { - const latestBuildInTimeline = latestBuild(context.builds) - const isUpdated = event.data?.latest_build.updated_at !== latestBuildInTimeline.updated_at - if (isUpdated) { - return send({ type: "REFRESH_TIMELINE" }) - } - } else { - return send({ type: "REFRESH_TIMELINE" }) - } + // if (event.type === "done.invoke.refreshWorkspace") { + // const latestBuildInTimeline = latestBuild(context.builds) + // const isUpdated = event.data?.latest_build.updated_at !== latestBuildInTimeline.updated_at + // if (isUpdated) { + // return send({ type: "REFRESH_TIMELINE" }) + // } + // } else { + // return send({ type: "REFRESH_TIMELINE" }) + // } }), }, guards: { @@ -650,28 +718,28 @@ export const workspaceMachine = createMachine( throw Error("Cannot cancel workspace without build id") } }, - refreshWorkspace: async (context) => { - if (context.workspace) { - return await API.getWorkspaceByOwnerAndName( - context.workspace.owner_name, - context.workspace.name, - { - include_deleted: true, - }, - ) - } else { - throw Error("Cannot refresh workspace without id") - } - }, - getResources: async (context) => { - // If the job hasn't completed, fetching resources will result - // in an unfriendly error for the user. - if (!context.workspace?.latest_build.job.completed_at) { - return [] - } - const resources = await API.getWorkspaceResources(context.workspace.latest_build.id) - return resources - }, + // refreshWorkspace: async (context) => { + // if (context.workspace) { + // return await API.getWorkspaceByOwnerAndName( + // context.workspace.owner_name, + // context.workspace.name, + // { + // include_deleted: true, + // }, + // ) + // } else { + // throw Error("Cannot refresh workspace without id") + // } + // }, + // getResources: async (context) => { + // // If the job hasn't completed, fetching resources will result + // // in an unfriendly error for the user. + // if (!context.workspace?.latest_build.job.completed_at) { + // return [] + // } + // const resources = await API.getWorkspaceResources(context.workspace.latest_build.id) + // return resources + // }, getBuilds: async (context) => { if (context.workspace) { return await API.getWorkspaceBuilds(context.workspace.id) diff --git a/site/webpack.dev.ts b/site/webpack.dev.ts index 9e59efa6aa3a9..dad40526220b7 100644 --- a/site/webpack.dev.ts +++ b/site/webpack.dev.ts @@ -74,6 +74,8 @@ const config: Configuration = { secure: false, }, }, + // We must disable compression to get SSEs to work (in workspaceXService.ts) + compress: false, static: ["./static"], }, From 24fdd1583bc988aee68ff036e6aa37cdb9fd4140 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Mon, 19 Sep 2022 19:58:57 +0000 Subject: [PATCH 2/6] workspace machine cleanup --- site/src/api/api.ts | 6 + .../src/pages/WorkspacePage/WorkspacePage.tsx | 38 --- .../xServices/workspace/workspaceXService.ts | 261 ++++++------------ 3 files changed, 91 insertions(+), 214 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index ea16315c3896e..c74cf48fa6064 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -203,6 +203,12 @@ export const getWorkspace = async ( return response.data } +export const watchWorkspace = (workspaceId: string): EventSource => { + return new EventSource(`${location.protocol}//${location.host}/api/v2/workspaces/${workspaceId}/watch`, + { withCredentials: true } + ) +} + export const getURLWithSearchParams = ( basePath: string, filter?: TypesGen.WorkspaceFilter | TypesGen.UsersRequest, diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index 387a5aeda449d..eaf9ce1117efb 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -51,44 +51,6 @@ export const WorkspacePage: FC = () => { cancellationError, } = workspaceState.context - useEffect(() => { - // console.log("workspace updating in component", workspace) - // console.log("resources", resources) - }, [workspace, resources]) - - // useEffect(() => { - // if (!workspace?.id) { - // return - // } - // - // console.log("hey im in here") - // const sse = new EventSource( - // `${location.protocol}//${location.host}/api/v2/workspaces/${workspace.id}/watch`, - // { withCredentials: true }, - // ) - // - // sse.onmessage = (e) => console.log("event", e) - // - // sse.addEventListener("ping", (event) => { - // console.log("ping event", event) - // }) - // - // sse.addEventListener("data", (event) => { - // console.log("data event", event) - // }) - // - // sse.onerror = (error) => { - // // error log here - // console.log("error", error) - // - // sse.close() - // } - // - // return () => { - // sse.close() - // } - // }, [workspace]) - const canUpdateWorkspace = Boolean(permissions?.updateWorkspace) const [bannerState, bannerSend] = useMachine(workspaceScheduleBannerMachine) diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index 9a94eb496d9eb..2b3185c1c4d42 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -5,12 +5,12 @@ import * as Types from "../../api/types" import * as TypesGen from "../../api/typesGenerated" import { displayError, displaySuccess } from "../../components/GlobalSnackbar/utils" -// const latestBuild = (builds: TypesGen.WorkspaceBuild[]) => { -// // Cloning builds to not change the origin object with the sort() -// return [...builds].sort((a, b) => { -// return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime() -// })[0] -// } +const latestBuild = (builds: TypesGen.WorkspaceBuild[]) => { + // Cloning builds to not change the origin object with the sort() + return [...builds].sort((a, b) => { + return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime() + })[0] +} const Language = { refreshTemplateError: "Error updating workspace: latest template could not be fetched.", @@ -20,8 +20,10 @@ const Language = { type Permissions = Record, boolean> export interface WorkspaceContext { - workspace?: TypesGen.Workspace | TypesGen.ServerSentEvent["data"] - template?: TypesGen.Template | TypesGen.ServerSentEvent["data"] + // our server side events instance + sse: EventSource | undefined, + workspace?: TypesGen.Workspace + template?: TypesGen.Template build?: TypesGen.WorkspaceBuild resources?: TypesGen.WorkspaceResource[] getWorkspaceError?: Error | unknown @@ -38,9 +40,9 @@ export interface WorkspaceContext { cancellationMessage?: Types.Message cancellationError?: Error | unknown // permissions - permissions?: Permissions | TypesGen.ServerSentEvent["data"] + permissions?: Permissions checkPermissionsError?: Error | unknown - userId?: string + userId?: string, } export type WorkspaceEvent = @@ -53,6 +55,7 @@ export type WorkspaceEvent = | { type: "UPDATE" } | { type: "CANCEL" } | { type: "LOAD_MORE_BUILDS" } + | { type: "CHECK_REFRESH_TIMELINE", data: TypesGen.ServerSentEvent["data"] } | { type: "REFRESH_TIMELINE" } | { type: "UPDATE_EVENT", data: TypesGen.ServerSentEvent["data"] } | { type: "SSE_ERROR", error: Error | unknown } @@ -111,12 +114,9 @@ export const workspaceMachine = createMachine( cancelWorkspace: { data: Types.Message } - refreshWorkspace: { - data: TypesGen.Workspace | undefined - } - getResources: { - data: TypesGen.WorkspaceResource[] - } + listenForEvents: { + data: TypesGen.ServerSentEvent + }, getBuilds: { data: TypesGen.WorkspaceBuild[] } @@ -199,90 +199,34 @@ export const workspaceMachine = createMachine( }, }, sseFailure: { - entry: ["updateGetWorkspaceError", "updateGetResourcesError"], + entry: ["assignRefreshWorkspaceError", "assignGetResourcesError"], after: { "1000": { - target: 'ready.watchEvents' + target: 'ready.listenForEvents' } } }, ready: { type: "parallel", states: { - watchEvents: { - context: { - test: '' - }, + listenForEvents: { on: { UPDATE_EVENT: { - actions: ["updateWorkspace", "updateResources"] + actions: ["updateWorkspace", "assignResources"] }, SSE_ERROR: { target: "#workspaceState.sseFailure" + }, + CHECK_REFRESH_TIMELINE: { + actions: ["refreshTimeline"] } }, - entry: ["clearGetWorkspaceError", "clearGetResourcesError"], + entry: ["clearGetWorkspaceError", "clearGetResourcesError", "initSse"], + exit: "closeSse", invoke: { - src: (context) => (callback) => { - if (!context.workspace.id) { - callback({ type: "SSE_ERROR", error: "Cannot refresh workspace without id" }) - } - - // open a new EventSource so we can stream SSE - const sse = new EventSource(`${location.protocol}//${location.host}/api/v2/workspaces/${context.workspace?.id}/watch`, - { withCredentials: true } - ) - - // update our data objects (workspace, resources) with each SSE that comes back from the server - sse.addEventListener("data", (event) => { - callback({ type: "UPDATE_EVENT", data: JSON.parse(event.data) }) - }) - - // handle any error events returned by our sse - sse.addEventListener("error", (event) => { - callback({ type: "SSE_ERROR", error: event }) - }) - - // handle any sse implementation exceptions - sse.onerror = () => { - sse.close(); - callback({ type: "SSE_ERROR", error: "sse error" }) - } - - }, + src: "listenForEvents", }, }, - // pollingWorkspace: { - // initial: "refreshingWorkspace", - // states: { - // refreshingWorkspace: { - // entry: "clearRefreshWorkspaceError", - // invoke: { - // src: "refreshWorkspace", - // id: "refreshWorkspace", - // onDone: [ - // { - // actions: ["refreshTimeline", "assignWorkspace"], - // target: "waiting", - // }, - // ], - // onError: [ - // { - // actions: "assignRefreshWorkspaceError", - // target: "waiting", - // }, - // ], - // }, - // }, - // waiting: { - // after: { - // "1000": { - // target: "refreshingWorkspace", - // }, - // }, - // }, - // }, - // }, build: { initial: "idle", states: { @@ -421,37 +365,6 @@ export const workspaceMachine = createMachine( }, }, }, - // pollingResources: { - // initial: "gettingResources", - // states: { - // gettingResources: { - // entry: "clearGetResourcesError", - // invoke: { - // src: "getResources", - // id: "getResources", - // onDone: [ - // { - // actions: "assignResources", - // target: "waiting", - // }, - // ], - // onError: [ - // { - // actions: "assignGetResourcesError", - // target: "waiting", - // }, - // ], - // }, - // }, - // waiting: { - // after: { - // "5000": { - // target: "gettingResources", - // }, - // }, - // }, - // }, - // }, timeline: { initial: "gettingBuilds", states: { @@ -531,18 +444,6 @@ export const workspaceMachine = createMachine( build: undefined, permissions: undefined, }), - updateWorkspace: assign({ - workspace: (_, event) => event.data, - }), - updateGetWorkspaceError: assign({ - getWorkspaceError: (_, event) => event, - }), - updateResources: assign({ - resources: (_, event) => event.data.latest_build.resources, - }), - updateGetResourcesError: assign({ - getResourcesError: (_, event) => event, - }), assignWorkspace: assign({ workspace: (_, event) => event.data, }), @@ -590,12 +491,29 @@ export const workspaceMachine = createMachine( clearCancellationError: assign({ cancellationError: (_) => undefined, }), - // assignRefreshWorkspaceError: assign({ - // refreshWorkspaceError: (_, event) => event.data, - // }), - // clearRefreshWorkspaceError: assign({ - // refreshWorkspaceError: (_) => undefined, - // }), + // SSE related actions + // open a new EventSource so we can stream SSE + initSse: assign({ + sse: (context) => context.workspace?.id ? API.watchWorkspace(context.workspace.id) : undefined + }), + closeSse: (context) => context.sse && context.sse.close(), + // updating workspaces + updateWorkspace: assign({ + workspace: (_, event) => event.data, + }), + assignRefreshWorkspaceError: assign({ + refreshWorkspaceError: (_, event) => event, + }), + // getting resources + assignResources: assign({ + resources: (_, event) => event.data.latest_build.resources, + }), + assignGetResourcesError: assign({ + getResourcesError: (_, event) => event, + }), + clearGetResourcesError: assign({ + getResourcesError: (_) => undefined, + }), assignRefreshTemplateError: assign({ refreshTemplateError: (_, event) => event.data, }), @@ -605,16 +523,6 @@ export const workspaceMachine = createMachine( clearRefreshTemplateError: assign({ refreshTemplateError: (_) => undefined, }), - // Resources - // assignResources: assign({ - // resources: (_, event) => event.data, - // }), - // assignGetResourcesError: assign({ - // getResourcesError: (_, event) => event.data, - // }), - clearGetResourcesError: assign({ - getResourcesError: (_) => undefined, - }), // Timeline assignBuilds: assign({ builds: (_, event) => event.data, @@ -643,25 +551,23 @@ export const workspaceMachine = createMachine( clearLoadMoreBuildsError: assign({ loadMoreBuildsError: (_) => undefined, }), - refreshTimeline: pure((context) => { + refreshTimeline: pure((context, event) => { // No need to refresh the timeline if it is not loaded if (!context.builds) { return } - // KIRA REVISIT - return send({ type: "REFRESH_TIMELINE" }) - // When it is a refresh workspace event, we want to check if the latest + // When it is a CHECK_REFRESH_TIMELINE workspace event, we want to check if the latest // build was updated to not over fetch the builds - // if (event.type === "done.invoke.refreshWorkspace") { - // const latestBuildInTimeline = latestBuild(context.builds) - // const isUpdated = event.data?.latest_build.updated_at !== latestBuildInTimeline.updated_at - // if (isUpdated) { - // return send({ type: "REFRESH_TIMELINE" }) - // } - // } else { - // return send({ type: "REFRESH_TIMELINE" }) - // } + if (event.type === "CHECK_REFRESH_TIMELINE") { + const latestBuildInTimeline = latestBuild(context.builds) + const isUpdated = event.data?.latest_build.updated_at !== latestBuildInTimeline.updated_at + if (isUpdated) { + return send({ type: "REFRESH_TIMELINE" }) + } + } else { + return send({ type: "REFRESH_TIMELINE" }) + } }), }, guards: { @@ -718,28 +624,31 @@ export const workspaceMachine = createMachine( throw Error("Cannot cancel workspace without build id") } }, - // refreshWorkspace: async (context) => { - // if (context.workspace) { - // return await API.getWorkspaceByOwnerAndName( - // context.workspace.owner_name, - // context.workspace.name, - // { - // include_deleted: true, - // }, - // ) - // } else { - // throw Error("Cannot refresh workspace without id") - // } - // }, - // getResources: async (context) => { - // // If the job hasn't completed, fetching resources will result - // // in an unfriendly error for the user. - // if (!context.workspace?.latest_build.job.completed_at) { - // return [] - // } - // const resources = await API.getWorkspaceResources(context.workspace.latest_build.id) - // return resources - // }, + listenForEvents: (context) => (callback) => { + if (!context.sse) { + callback({ type: "SSE_ERROR", error: "error initializing sse" }) + return + } + + context.sse.addEventListener("data", (event) => { + // update our data objects (workspace, resources) with each SSE that comes back from the server + callback({ type: "UPDATE_EVENT", data: JSON.parse(event.data) }) + // refresh our timeline + callback({ type: "CHECK_REFRESH_TIMELINE", data: JSON.parse(event.data) }) + }) + + // handle any error events returned by our sse + context.sse.addEventListener("error", (event) => { + callback({ type: "SSE_ERROR", error: event }) + }) + + // handle any sse implementation exceptions + context.sse.onerror = () => { + context.sse && context.sse.close(); + callback({ type: "SSE_ERROR", error: "sse error" }) + } + + }, getBuilds: async (context) => { if (context.workspace) { return await API.getWorkspaceBuilds(context.workspace.id) From a3bc55e097c7297d16848693e4070c95c577d080 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Tue, 20 Sep 2022 15:05:31 +0000 Subject: [PATCH 3/6] renaming callback --- site/src/xServices/workspace/workspaceXService.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index 2b3185c1c4d42..5fa454b2c5e03 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -624,28 +624,28 @@ export const workspaceMachine = createMachine( throw Error("Cannot cancel workspace without build id") } }, - listenForEvents: (context) => (callback) => { + listenForEvents: (context) => (send) => { if (!context.sse) { - callback({ type: "SSE_ERROR", error: "error initializing sse" }) + send({ type: "SSE_ERROR", error: "error initializing sse" }) return } context.sse.addEventListener("data", (event) => { // update our data objects (workspace, resources) with each SSE that comes back from the server - callback({ type: "UPDATE_EVENT", data: JSON.parse(event.data) }) + send({ type: "UPDATE_EVENT", data: JSON.parse(event.data) }) // refresh our timeline - callback({ type: "CHECK_REFRESH_TIMELINE", data: JSON.parse(event.data) }) + send({ type: "CHECK_REFRESH_TIMELINE", data: JSON.parse(event.data) }) }) // handle any error events returned by our sse context.sse.addEventListener("error", (event) => { - callback({ type: "SSE_ERROR", error: event }) + send({ type: "SSE_ERROR", error: event }) }) // handle any sse implementation exceptions context.sse.onerror = () => { context.sse && context.sse.close(); - callback({ type: "SSE_ERROR", error: "sse error" }) + send({ type: "SSE_ERROR", error: "sse error" }) } }, From f24d70b29ac6141b7e452b2cbbf8330ff8eb2612 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Tue, 20 Sep 2022 18:17:59 +0000 Subject: [PATCH 4/6] general cleanup --- site/src/api/api.ts | 5 + .../src/pages/WorkspacePage/WorkspacePage.tsx | 11 +- .../xServices/workspace/workspaceXService.ts | 140 +++++++++--------- 3 files changed, 78 insertions(+), 78 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index c74cf48fa6064..cb32b6b7ad8d8 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -203,6 +203,11 @@ export const getWorkspace = async ( return response.data } +/** + * + * @param workspaceId + * @returns An EventSource that emits workspace event objects (ServerSentEvent) + */ export const watchWorkspace = (workspaceId: string): EventSource => { return new EventSource(`${location.protocol}//${location.host}/api/v2/workspaces/${workspaceId}/watch`, { withCredentials: true } diff --git a/site/src/pages/WorkspacePage/WorkspacePage.tsx b/site/src/pages/WorkspacePage/WorkspacePage.tsx index eaf9ce1117efb..71aff897ba9c4 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.tsx @@ -40,9 +40,8 @@ export const WorkspacePage: FC = () => { workspace, getWorkspaceError, template, - refreshTemplateError, - resources, - getResourcesError, + refreshTemplateWarning, + refreshWorkspaceWarning, builds, getBuildsError, permissions, @@ -70,7 +69,7 @@ export const WorkspacePage: FC = () => { return (
{Boolean(getWorkspaceError) && } - {Boolean(refreshTemplateError) && } + {Boolean(refreshTemplateWarning) && } {Boolean(checkPermissionsError) && }
) @@ -128,11 +127,11 @@ export const WorkspacePage: FC = () => { handleDelete={() => workspaceSend("ASK_DELETE")} handleUpdate={() => workspaceSend("UPDATE")} handleCancel={() => workspaceSend("CANCEL")} - resources={resources} + resources={workspace.latest_build.resources} builds={builds} canUpdateWorkspace={canUpdateWorkspace} workspaceErrors={{ - [WorkspaceErrors.GET_RESOURCES_ERROR]: getResourcesError, + [WorkspaceErrors.GET_RESOURCES_ERROR]: refreshWorkspaceWarning, [WorkspaceErrors.GET_BUILDS_ERROR]: getBuildsError, [WorkspaceErrors.BUILD_ERROR]: buildError, [WorkspaceErrors.CANCELLATION_ERROR]: cancellationError, diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index 5fa454b2c5e03..3f4bf8813a75a 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -13,7 +13,7 @@ const latestBuild = (builds: TypesGen.WorkspaceBuild[]) => { } const Language = { - refreshTemplateError: "Error updating workspace: latest template could not be fetched.", + refreshTemplateWarning: "Error updating workspace: latest template could not be fetched.", buildError: "Workspace action failed.", } @@ -21,22 +21,20 @@ type Permissions = Record, boolean> export interface WorkspaceContext { // our server side events instance - sse: EventSource | undefined, + eventSource?: EventSource, workspace?: TypesGen.Workspace template?: TypesGen.Template build?: TypesGen.WorkspaceBuild - resources?: TypesGen.WorkspaceResource[] getWorkspaceError?: Error | unknown - // error creating a new WorkspaceBuild - buildError?: Error | unknown - // these are separate from getX errors because they don't make the page unusable - refreshWorkspaceError: Error | unknown - refreshTemplateError: Error | unknown - getResourcesError: Error | unknown + // these are labeled as warnings because they don't make the page unusable + refreshWorkspaceWarning?: Error | unknown + refreshTemplateWarning: Error | unknown // Builds builds?: TypesGen.WorkspaceBuild[] getBuildsError?: Error | unknown loadMoreBuildsError?: Error | unknown + // error creating a new WorkspaceBuild + buildError?: Error | unknown cancellationMessage?: Types.Message cancellationError?: Error | unknown // permissions @@ -47,6 +45,7 @@ export interface WorkspaceContext { export type WorkspaceEvent = | { type: "GET_WORKSPACE"; workspaceName: string; username: string } + | { type: "REFRESH_WORKSPACE", data: TypesGen.ServerSentEvent["data"] } | { type: "START" } | { type: "STOP" } | { type: "ASK_DELETE" } @@ -57,8 +56,7 @@ export type WorkspaceEvent = | { type: "LOAD_MORE_BUILDS" } | { type: "CHECK_REFRESH_TIMELINE", data: TypesGen.ServerSentEvent["data"] } | { type: "REFRESH_TIMELINE" } - | { type: "UPDATE_EVENT", data: TypesGen.ServerSentEvent["data"] } - | { type: "SSE_ERROR", error: Error | unknown } + | { type: "EVENT_SOURCE_ERROR", error: Error | unknown } export const checks = { readWorkspace: "readWorkspace", @@ -114,7 +112,7 @@ export const workspaceMachine = createMachine( cancelWorkspace: { data: Types.Message } - listenForEvents: { + listening: { data: TypesGen.ServerSentEvent }, getBuilds: { @@ -160,7 +158,7 @@ export const workspaceMachine = createMachine( tags: "loading", }, refreshingTemplate: { - entry: "clearRefreshTemplateError", + entry: "clearRefreshTemplateWarning", invoke: { src: "getTemplate", id: "refreshTemplate", @@ -172,7 +170,7 @@ export const workspaceMachine = createMachine( ], onError: [ { - actions: ["assignRefreshTemplateError", "displayRefreshTemplateError"], + actions: ["assignRefreshTemplateWarning", "displayRefreshTemplateWarning"], target: "error", }, ], @@ -198,34 +196,39 @@ export const workspaceMachine = createMachine( ], }, }, - sseFailure: { - entry: ["assignRefreshWorkspaceError", "assignGetResourcesError"], - after: { - "1000": { - target: 'ready.listenForEvents' - } - } - }, ready: { type: "parallel", states: { - listenForEvents: { - on: { - UPDATE_EVENT: { - actions: ["updateWorkspace", "assignResources"] - }, - SSE_ERROR: { - target: "#workspaceState.sseFailure" + listening: { + initial: "gettingEvents", + states: { + gettingEvents: { + entry: ['clearRefreshWorkspaceWarning', 'initializeEventSource'], + exit: "closeEventSource", + invoke: { + src: "listening", + }, + on: { + REFRESH_WORKSPACE: { + actions: ["refreshWorkspace"] + }, + EVENT_SOURCE_ERROR: { + target: "error" + }, + CHECK_REFRESH_TIMELINE: { + actions: ["refreshTimeline"] + } + }, }, - CHECK_REFRESH_TIMELINE: { - actions: ["refreshTimeline"] + error: { + entry: "assignRefreshWorkspaceWarning", + after: { + "1000": { + target: 'gettingEvents' + } + } } - }, - entry: ["clearGetWorkspaceError", "clearGetResourcesError", "initSse"], - exit: "closeSse", - invoke: { - src: "listenForEvents", - }, + } }, build: { initial: "idle", @@ -345,7 +348,7 @@ export const workspaceMachine = createMachine( }, }, refreshingTemplate: { - entry: "clearRefreshTemplateError", + entry: "clearRefreshTemplateWarning", invoke: { src: "getTemplate", id: "refreshTemplate", @@ -357,7 +360,7 @@ export const workspaceMachine = createMachine( ], onError: [ { - actions: ["assignRefreshTemplateError", "displayRefreshTemplateError"], + actions: ["assignRefreshTemplateWarning", "displayRefreshTemplateWarning"], target: "idle", }, ], @@ -443,6 +446,7 @@ export const workspaceMachine = createMachine( template: undefined, build: undefined, permissions: undefined, + eventSource: undefined, }), assignWorkspace: assign({ workspace: (_, event) => event.data, @@ -493,35 +497,27 @@ export const workspaceMachine = createMachine( }), // SSE related actions // open a new EventSource so we can stream SSE - initSse: assign({ - sse: (context) => context.workspace?.id ? API.watchWorkspace(context.workspace.id) : undefined + initializeEventSource: assign({ + eventSource: (context) => context.workspace && API.watchWorkspace(context.workspace.id) }), - closeSse: (context) => context.sse && context.sse.close(), - // updating workspaces - updateWorkspace: assign({ + closeEventSource: (context) => context.eventSource && context.eventSource.close(), + refreshWorkspace: assign({ workspace: (_, event) => event.data, }), - assignRefreshWorkspaceError: assign({ - refreshWorkspaceError: (_, event) => event, - }), - // getting resources - assignResources: assign({ - resources: (_, event) => event.data.latest_build.resources, - }), - assignGetResourcesError: assign({ - getResourcesError: (_, event) => event, + assignRefreshWorkspaceWarning: assign({ + refreshWorkspaceWarning: (_, event) => event, }), - clearGetResourcesError: assign({ - getResourcesError: (_) => undefined, + clearRefreshWorkspaceWarning: assign({ + refreshWorkspaceWarning: (_) => undefined, }), - assignRefreshTemplateError: assign({ - refreshTemplateError: (_, event) => event.data, + assignRefreshTemplateWarning: assign({ + refreshTemplateWarning: (_, event) => event.data, }), - displayRefreshTemplateError: () => { - displayError(Language.refreshTemplateError) + displayRefreshTemplateWarning: () => { + displayError(Language.refreshTemplateWarning) }, - clearRefreshTemplateError: assign({ - refreshTemplateError: (_) => undefined, + clearRefreshTemplateWarning: assign({ + refreshTemplateWarning: (_) => undefined, }), // Timeline assignBuilds: assign({ @@ -624,28 +620,28 @@ export const workspaceMachine = createMachine( throw Error("Cannot cancel workspace without build id") } }, - listenForEvents: (context) => (send) => { - if (!context.sse) { - send({ type: "SSE_ERROR", error: "error initializing sse" }) + listening: (context) => (send) => { + if (!context.eventSource) { + send({ type: "EVENT_SOURCE_ERROR", error: "error initializing sse" }) return } - context.sse.addEventListener("data", (event) => { - // update our data objects (workspace, resources) with each SSE that comes back from the server - send({ type: "UPDATE_EVENT", data: JSON.parse(event.data) }) + context.eventSource.addEventListener("data", (event) => { + // refresh our workspace with each SSE + send({ type: "REFRESH_WORKSPACE", data: JSON.parse(event.data) }) // i wonder if this is problematic // refresh our timeline send({ type: "CHECK_REFRESH_TIMELINE", data: JSON.parse(event.data) }) }) // handle any error events returned by our sse - context.sse.addEventListener("error", (event) => { - send({ type: "SSE_ERROR", error: event }) + context.eventSource.addEventListener("error", (event) => { + send({ type: "EVENT_SOURCE_ERROR", error: event }) }) // handle any sse implementation exceptions - context.sse.onerror = () => { - context.sse && context.sse.close(); - send({ type: "SSE_ERROR", error: "sse error" }) + context.eventSource.onerror = () => { + context.eventSource && context.eventSource.close(); + send({ type: "EVENT_SOURCE_ERROR", error: "sse error" }) } }, From 80c5fac4a0773a801a4e96b1f5014e4aea0ab952 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Wed, 21 Sep 2022 15:24:24 +0000 Subject: [PATCH 5/6] fixed tests --- site/package.json | 1 + site/src/@types/eventsourcemock.d.ts | 1 + site/src/api/api.ts | 9 +++-- .../WorkspacePage/WorkspacePage.test.tsx | 36 +++++++++++++++++- site/src/testHelpers/entities.ts | 2 +- .../xServices/workspace/workspaceXService.ts | 37 +++++++++---------- site/yarn.lock | 5 +++ 7 files changed, 65 insertions(+), 26 deletions(-) create mode 100644 site/src/@types/eventsourcemock.d.ts diff --git a/site/package.json b/site/package.json index 513887ce4152c..1c6e11886da27 100644 --- a/site/package.json +++ b/site/package.json @@ -43,6 +43,7 @@ "cronstrue": "2.11.0", "dayjs": "1.11.4", "emoji-mart": "^5.2.1", + "eventsourcemock": "^2.0.0", "formik": "^2.2.9", "front-matter": "4.0.2", "history": "5.3.0", diff --git a/site/src/@types/eventsourcemock.d.ts b/site/src/@types/eventsourcemock.d.ts new file mode 100644 index 0000000000000..12f9cc003ebf0 --- /dev/null +++ b/site/src/@types/eventsourcemock.d.ts @@ -0,0 +1 @@ +declare module "eventsourcemock" diff --git a/site/src/api/api.ts b/site/src/api/api.ts index cb32b6b7ad8d8..a52e73cf72152 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -204,13 +204,14 @@ export const getWorkspace = async ( } /** - * - * @param workspaceId + * + * @param workspaceId * @returns An EventSource that emits workspace event objects (ServerSentEvent) */ export const watchWorkspace = (workspaceId: string): EventSource => { - return new EventSource(`${location.protocol}//${location.host}/api/v2/workspaces/${workspaceId}/watch`, - { withCredentials: true } + return new EventSource( + `${location.protocol}//${location.host}/api/v2/workspaces/${workspaceId}/watch`, + { withCredentials: true }, ) } diff --git a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx index c6754ab026fd1..e8c68cb123511 100644 --- a/site/src/pages/WorkspacePage/WorkspacePage.test.tsx +++ b/site/src/pages/WorkspacePage/WorkspacePage.test.tsx @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ import { fireEvent, screen, waitFor, within } from "@testing-library/react" +import EventSource from "eventsourcemock" import i18next from "i18next" import { rest } from "msw" import * as api from "../../api/api" @@ -22,6 +23,7 @@ import { MockWorkspaceAgentConnecting, MockWorkspaceAgentDisconnected, MockWorkspaceBuild, + MockWorkspaceResource2, renderWithAuth, } from "../../testHelpers/renderHelpers" import { server } from "../../testHelpers/server" @@ -70,6 +72,11 @@ const testStatus = async (ws: Workspace, label: string) => { beforeEach(() => { jest.resetAllMocks() + + // mocking out EventSource for SSE + Object.defineProperty(window, "EventSource", { + value: EventSource, + }) }) describe("WorkspacePage", () => { @@ -194,18 +201,43 @@ describe("WorkspacePage", () => { describe("Resources", () => { it("shows the status of each agent in each resource", async () => { const getTemplateMock = jest.spyOn(api, "getTemplate").mockResolvedValueOnce(MockTemplate) + + const workspaceWithResources = { + ...MockWorkspace, + latest_build: { + ...MockWorkspaceBuild, + resources: [ + { + ...MockWorkspaceResource2, + agents: [ + MockWorkspaceAgent, + MockWorkspaceAgentDisconnected, + MockWorkspaceAgentConnecting, + ], + }, + ], + }, + } + + server.use( + rest.get(`/api/v2/users/:username/workspace/:workspaceName`, (req, res, ctx) => { + return res(ctx.status(200), ctx.json(workspaceWithResources)) + }), + ) + renderWithAuth(, { route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}`, path: "/@:username/:workspace", }) + const agent1Names = await screen.findAllByText(MockWorkspaceAgent.name) - expect(agent1Names.length).toEqual(2) + expect(agent1Names.length).toEqual(1) const agent2Names = await screen.findAllByText(MockWorkspaceAgentDisconnected.name) expect(agent2Names.length).toEqual(2) const agent1Status = await screen.findAllByText( DisplayAgentStatusLanguage[MockWorkspaceAgent.status], ) - expect(agent1Status.length).toEqual(4) + expect(agent1Status.length).toEqual(1) const agentDisconnected = await screen.findAllByText( DisplayAgentStatusLanguage[MockWorkspaceAgentDisconnected.status], ) diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index 598d37753a71f..04183a3ddfbd7 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -380,7 +380,7 @@ export const MockWorkspaceAgentOutdated: TypesGen.WorkspaceAgent = { export const MockWorkspaceAgentConnecting: TypesGen.WorkspaceAgent = { ...MockWorkspaceAgent, - id: "test-workspace-agent-2", + id: "test-workspace-agent-connecting", name: "another-workspace-agent", status: "connecting", version: "", diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index 3f4bf8813a75a..ba88cfa65d5d6 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -21,7 +21,7 @@ type Permissions = Record, boolean> export interface WorkspaceContext { // our server side events instance - eventSource?: EventSource, + eventSource?: EventSource workspace?: TypesGen.Workspace template?: TypesGen.Template build?: TypesGen.WorkspaceBuild @@ -40,12 +40,12 @@ export interface WorkspaceContext { // permissions permissions?: Permissions checkPermissionsError?: Error | unknown - userId?: string, + userId?: string } export type WorkspaceEvent = | { type: "GET_WORKSPACE"; workspaceName: string; username: string } - | { type: "REFRESH_WORKSPACE", data: TypesGen.ServerSentEvent["data"] } + | { type: "REFRESH_WORKSPACE"; data: TypesGen.ServerSentEvent["data"] } | { type: "START" } | { type: "STOP" } | { type: "ASK_DELETE" } @@ -54,9 +54,9 @@ export type WorkspaceEvent = | { type: "UPDATE" } | { type: "CANCEL" } | { type: "LOAD_MORE_BUILDS" } - | { type: "CHECK_REFRESH_TIMELINE", data: TypesGen.ServerSentEvent["data"] } + | { type: "CHECK_REFRESH_TIMELINE"; data: TypesGen.ServerSentEvent["data"] } | { type: "REFRESH_TIMELINE" } - | { type: "EVENT_SOURCE_ERROR", error: Error | unknown } + | { type: "EVENT_SOURCE_ERROR"; error: Error | unknown } export const checks = { readWorkspace: "readWorkspace", @@ -114,7 +114,7 @@ export const workspaceMachine = createMachine( } listening: { data: TypesGen.ServerSentEvent - }, + } getBuilds: { data: TypesGen.WorkspaceBuild[] } @@ -203,32 +203,32 @@ export const workspaceMachine = createMachine( initial: "gettingEvents", states: { gettingEvents: { - entry: ['clearRefreshWorkspaceWarning', 'initializeEventSource'], + entry: ["clearRefreshWorkspaceWarning", "initializeEventSource"], exit: "closeEventSource", invoke: { src: "listening", }, on: { REFRESH_WORKSPACE: { - actions: ["refreshWorkspace"] + actions: ["refreshWorkspace"], }, EVENT_SOURCE_ERROR: { - target: "error" + target: "error", }, CHECK_REFRESH_TIMELINE: { - actions: ["refreshTimeline"] - } + actions: ["refreshTimeline"], + }, }, }, error: { entry: "assignRefreshWorkspaceWarning", after: { "1000": { - target: 'gettingEvents' - } - } - } - } + target: "gettingEvents", + }, + }, + }, + }, }, build: { initial: "idle", @@ -498,7 +498,7 @@ export const workspaceMachine = createMachine( // SSE related actions // open a new EventSource so we can stream SSE initializeEventSource: assign({ - eventSource: (context) => context.workspace && API.watchWorkspace(context.workspace.id) + eventSource: (context) => context.workspace && API.watchWorkspace(context.workspace.id), }), closeEventSource: (context) => context.eventSource && context.eventSource.close(), refreshWorkspace: assign({ @@ -640,10 +640,9 @@ export const workspaceMachine = createMachine( // handle any sse implementation exceptions context.eventSource.onerror = () => { - context.eventSource && context.eventSource.close(); + context.eventSource && context.eventSource.close() send({ type: "EVENT_SOURCE_ERROR", error: "sse error" }) } - }, getBuilds: async (context) => { if (context.workspace) { diff --git a/site/yarn.lock b/site/yarn.lock index 8680851774568..c14c11bbe9ce6 100644 --- a/site/yarn.lock +++ b/site/yarn.lock @@ -7144,6 +7144,11 @@ events@^3.0.0, events@^3.2.0, events@^3.3.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +eventsourcemock@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eventsourcemock/-/eventsourcemock-2.0.0.tgz#83f66bc537e4909ef385bf84272e300737954ef0" + integrity sha512-tSmJnuE+h6A8/hLRg0usf1yL+Q8w01RQtmg0Uzgoxk/HIPZrIUeAr/A4es/8h1wNsoG8RdiESNQLTKiNwbSC3Q== + evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" From 1cfe862317f4112eb3f13122848feaf58a0e1cd3 Mon Sep 17 00:00:00 2001 From: Kira Pilot Date: Wed, 21 Sep 2022 17:26:33 +0000 Subject: [PATCH 6/6] PR comments --- site/src/xServices/workspace/workspaceXService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/xServices/workspace/workspaceXService.ts b/site/src/xServices/workspace/workspaceXService.ts index ba88cfa65d5d6..144f128670779 100644 --- a/site/src/xServices/workspace/workspaceXService.ts +++ b/site/src/xServices/workspace/workspaceXService.ts @@ -207,6 +207,7 @@ export const workspaceMachine = createMachine( exit: "closeEventSource", invoke: { src: "listening", + id: "listening", }, on: { REFRESH_WORKSPACE: { @@ -628,7 +629,7 @@ export const workspaceMachine = createMachine( context.eventSource.addEventListener("data", (event) => { // refresh our workspace with each SSE - send({ type: "REFRESH_WORKSPACE", data: JSON.parse(event.data) }) // i wonder if this is problematic + send({ type: "REFRESH_WORKSPACE", data: JSON.parse(event.data) }) // refresh our timeline send({ type: "CHECK_REFRESH_TIMELINE", data: JSON.parse(event.data) }) }) @@ -640,7 +641,6 @@ export const workspaceMachine = createMachine( // handle any sse implementation exceptions context.eventSource.onerror = () => { - context.eventSource && context.eventSource.close() send({ type: "EVENT_SOURCE_ERROR", error: "sse error" }) } },