From b921eb951393db1d3d2f729f91feb18ab4e1f796 Mon Sep 17 00:00:00 2001 From: Asher Date: Thu, 17 Aug 2023 23:12:34 -0800 Subject: [PATCH 1/4] Avoid getting workspaces when not logged in Otherwise you get some confusing ECONNREFUSED errors in the logs. --- src/workspacesProvider.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 115d3f80..3c6c04f5 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -16,6 +16,10 @@ export class WorkspaceProvider implements vscode.TreeDataProvider = {} constructor(private readonly getWorkspacesQuery: WorkspaceQuery, private readonly storage: Storage) { + if (!storage.getURL()) { + // Not logged in. + return + } getWorkspaces({ q: this.getWorkspacesQuery }) .then((workspaces) => { const workspacesTreeItem: WorkspaceTreeItem[] = [] From 78ff33c258d8e5350585342ba74f3836bd7dd825 Mon Sep 17 00:00:00 2001 From: Asher Date: Thu, 17 Aug 2023 23:31:19 -0800 Subject: [PATCH 2/4] Make workspaces refresh load new workspaces Currently the refresh button does exactly nothing. This will also let us load new workspaces in other cases, like when logging in or out. --- src/extension.ts | 4 ++-- src/workspacesProvider.ts | 46 ++++++++++++++++++--------------------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 931f2995..7345569b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -138,8 +138,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { commands.navigateToWorkspaceSettings.bind(commands), ) vscode.commands.registerCommand("coder.refreshWorkspaces", () => { - myWorkspacesProvider.refresh() - allWorkspacesProvider.refresh() + myWorkspacesProvider.fetchAndRefresh() + allWorkspacesProvider.fetchAndRefresh() }) // Since the "onResolveRemoteAuthority:ssh-remote" activation event exists diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 3c6c04f5..8ca84f34 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -16,32 +16,27 @@ export class WorkspaceProvider implements vscode.TreeDataProvider = {} constructor(private readonly getWorkspacesQuery: WorkspaceQuery, private readonly storage: Storage) { - if (!storage.getURL()) { - // Not logged in. - return - } - getWorkspaces({ q: this.getWorkspacesQuery }) - .then((workspaces) => { - const workspacesTreeItem: WorkspaceTreeItem[] = [] - workspaces.workspaces.forEach((workspace) => { - const showMetadata = this.getWorkspacesQuery === WorkspaceQuery.Mine - if (showMetadata) { - const agents = extractAgents(workspace) - agents.forEach((agent) => this.monitorMetadata(agent.id)) // monitor metadata for all agents - } - const treeItem = new WorkspaceTreeItem( - workspace, - this.getWorkspacesQuery === WorkspaceQuery.All, - showMetadata, - ) - workspacesTreeItem.push(treeItem) - }) - return workspacesTreeItem - }) - .then((workspaces) => { - this.workspaces = workspaces - this.refresh() + this.fetchAndRefresh() + } + + // fetchAndRefrehsh fetches new workspaces then re-renders the entire tree. + async fetchAndRefresh() { + const workspacesTreeItem: WorkspaceTreeItem[] = [] + // If the URL is set then we are logged in. + if (this.storage.getURL()) { + const resp = await getWorkspaces({ q: this.getWorkspacesQuery }) + resp.workspaces.forEach((workspace) => { + const showMetadata = this.getWorkspacesQuery === WorkspaceQuery.Mine + if (showMetadata) { + const agents = extractAgents(workspace) + agents.forEach((agent) => this.monitorMetadata(agent.id)) // monitor metadata for all agents + } + const treeItem = new WorkspaceTreeItem(workspace, this.getWorkspacesQuery === WorkspaceQuery.All, showMetadata) + workspacesTreeItem.push(treeItem) }) + } + this.workspaces = workspacesTreeItem + this.refresh() } private _onDidChangeTreeData: vscode.EventEmitter = @@ -49,6 +44,7 @@ export class WorkspaceProvider implements vscode.TreeDataProvider = this._onDidChangeTreeData.event + // refresh causes the tree to re-render. It does not fetch fresh workspaces. refresh(item: vscode.TreeItem | undefined | null | void): void { this._onDidChangeTreeData.fire(item) } From cb95d39cd2b2b3b44c47abe6f5f4bd30d7982596 Mon Sep 17 00:00:00 2001 From: Asher Date: Thu, 17 Aug 2023 23:34:43 -0800 Subject: [PATCH 3/4] Refresh workspaces when logging in and out I moved the async call out to make it easier to avoid adding new watches if we log out while fetching workspaces. --- src/commands.ts | 2 ++ src/workspacesProvider.ts | 31 +++++++++++++++++++++---------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index 683e4f08..14cb6d7a 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -108,6 +108,7 @@ export class Commands { vscode.commands.executeCommand("coder.open") } }) + vscode.commands.executeCommand("coder.refreshWorkspaces") } catch (error) { vscode.window.showErrorMessage("Failed to authenticate with Coder: " + error) } @@ -122,6 +123,7 @@ export class Commands { vscode.commands.executeCommand("coder.login") } }) + vscode.commands.executeCommand("coder.refreshWorkspaces") } public async createWorkspace(): Promise { diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index 8ca84f34..e2a092fe 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -13,7 +13,7 @@ export enum WorkspaceQuery { export class WorkspaceProvider implements vscode.TreeDataProvider { private workspaces: WorkspaceTreeItem[] = [] - private agentMetadata: Record = {} + private agentWatchers: Record void; metadata?: AgentMetadataEvent[] }> = {} constructor(private readonly getWorkspacesQuery: WorkspaceQuery, private readonly storage: Storage) { this.fetchAndRefresh() @@ -21,15 +21,17 @@ export class WorkspaceProvider implements vscode.TreeDataProvider watcher.dispose()) // If the URL is set then we are logged in. if (this.storage.getURL()) { const resp = await getWorkspaces({ q: this.getWorkspacesQuery }) resp.workspaces.forEach((workspace) => { const showMetadata = this.getWorkspacesQuery === WorkspaceQuery.Mine - if (showMetadata) { + if (showMetadata && token) { const agents = extractAgents(workspace) - agents.forEach((agent) => this.monitorMetadata(agent.id)) // monitor metadata for all agents + agents.forEach((agent) => this.monitorMetadata(agent.id, token)) // monitor metadata for all agents } const treeItem = new WorkspaceTreeItem(workspace, this.getWorkspacesQuery === WorkspaceQuery.All, showMetadata) workspacesTreeItem.push(treeItem) @@ -62,7 +64,7 @@ export class WorkspaceProvider implements vscode.TreeDataProvider new AgentMetadataTreeItem(metadata))) } @@ -71,30 +73,39 @@ export class WorkspaceProvider implements vscode.TreeDataProvider { + // monitorMetadata opens a web socket to monitor metadata on the specified + // agent and registers a disposer that can be used to stop the watch. + monitorMetadata(agentId: WorkspaceAgent["id"], token: string): void { const agentMetadataURL = new URL(`${this.storage.getURL()}/api/v2/workspaceagents/${agentId}/watch-metadata`) const agentMetadataEventSource = new EventSource(agentMetadataURL.toString(), { headers: { - "Coder-Session-Token": await this.storage.getSessionToken(), + "Coder-Session-Token": token, }, }) + this.agentWatchers[agentId] = { + dispose: () => { + delete this.agentWatchers[agentId] + agentMetadataEventSource.close() + }, + } + agentMetadataEventSource.addEventListener("data", (event) => { try { const dataEvent = JSON.parse(event.data) const agentMetadata = AgentMetadataEventSchemaArray.parse(dataEvent) if (agentMetadata.length === 0) { - agentMetadataEventSource.close() + this.agentWatchers[agentId].dispose() } - const savedMetadata = this.agentMetadata[agentId] + const savedMetadata = this.agentWatchers[agentId].metadata if (JSON.stringify(savedMetadata) !== JSON.stringify(agentMetadata)) { - this.agentMetadata[agentId] = agentMetadata // overwrite existing metadata + this.agentWatchers[agentId].metadata = agentMetadata // overwrite existing metadata this.refresh() } } catch (error) { - agentMetadataEventSource.close() + this.agentWatchers[agentId].dispose() } }) } From 87c3e999a4160d92eaa32adbb9d6f90ace6067f7 Mon Sep 17 00:00:00 2001 From: Asher Date: Fri, 18 Aug 2023 08:04:27 -0800 Subject: [PATCH 4/4] Actually is using server-sent events --- src/workspacesProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workspacesProvider.ts b/src/workspacesProvider.ts index e2a092fe..3245b767 100644 --- a/src/workspacesProvider.ts +++ b/src/workspacesProvider.ts @@ -73,7 +73,7 @@ export class WorkspaceProvider implements vscode.TreeDataProvider