@@ -13,38 +13,40 @@ export enum WorkspaceQuery {
1313
1414export class WorkspaceProvider implements vscode . TreeDataProvider < vscode . TreeItem > {
1515private workspaces :WorkspaceTreeItem [ ] = [ ]
16- private agentMetadata :Record < WorkspaceAgent [ "id" ] , AgentMetadataEvent [ ] > = { }
16+ private agentWatchers :Record < WorkspaceAgent [ "id" ] , { dispose : ( ) => void ; metadata ?: AgentMetadataEvent [ ] } > = { }
1717
1818constructor ( private readonly getWorkspacesQuery :WorkspaceQuery , private readonly storage :Storage ) {
19- getWorkspaces ( { q :this . getWorkspacesQuery } )
20- . then ( ( workspaces ) => {
21- const workspacesTreeItem :WorkspaceTreeItem [ ] = [ ]
22- workspaces . workspaces . forEach ( ( workspace ) => {
23- const showMetadata = this . getWorkspacesQuery === WorkspaceQuery . Mine
24- if ( showMetadata ) {
25- const agents = extractAgents ( workspace )
26- agents . forEach ( ( agent ) => this . monitorMetadata ( agent . id ) ) // monitor metadata for all agents
27- }
28- const treeItem = new WorkspaceTreeItem (
29- workspace ,
30- this . getWorkspacesQuery === WorkspaceQuery . All ,
31- showMetadata ,
32- )
33- workspacesTreeItem . push ( treeItem )
34- } )
35- return workspacesTreeItem
36- } )
37- . then ( ( workspaces ) => {
38- this . workspaces = workspaces
39- this . refresh ( )
19+ this . fetchAndRefresh ( )
20+ }
21+
22+ // fetchAndRefrehsh fetches new workspaces then re-renders the entire tree.
23+ async fetchAndRefresh ( ) {
24+ const token = await this . storage . getSessionToken ( )
25+ const workspacesTreeItem :WorkspaceTreeItem [ ] = [ ]
26+ Object . values ( this . agentWatchers ) . forEach ( ( watcher ) => watcher . dispose ( ) )
27+ // If the URL is set then we are logged in.
28+ if ( this . storage . getURL ( ) ) {
29+ const resp = await getWorkspaces ( { q :this . getWorkspacesQuery } )
30+ resp . workspaces . forEach ( ( workspace ) => {
31+ const showMetadata = this . getWorkspacesQuery === WorkspaceQuery . Mine
32+ if ( showMetadata && token ) {
33+ const agents = extractAgents ( workspace )
34+ agents . forEach ( ( agent ) => this . monitorMetadata ( agent . id , token ) ) // monitor metadata for all agents
35+ }
36+ const treeItem = new WorkspaceTreeItem ( workspace , this . getWorkspacesQuery === WorkspaceQuery . All , showMetadata )
37+ workspacesTreeItem . push ( treeItem )
4038} )
39+ }
40+ this . workspaces = workspacesTreeItem
41+ this . refresh ( )
4142}
4243
4344private _onDidChangeTreeData :vscode . EventEmitter < vscode . TreeItem | undefined | null | void > =
4445new vscode . EventEmitter < vscode . TreeItem | undefined | null | void > ( )
4546readonly onDidChangeTreeData :vscode . Event < vscode . TreeItem | undefined | null | void > =
4647this . _onDidChangeTreeData . event
4748
49+ // refresh causes the tree to re-render. It does not fetch fresh workspaces.
4850refresh ( item :vscode . TreeItem | undefined | null | void ) :void {
4951this . _onDidChangeTreeData . fire ( item )
5052}
@@ -62,7 +64,7 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeIte
6264)
6365return Promise . resolve ( agentTreeItems )
6466} else if ( element instanceof AgentTreeItem ) {
65- const savedMetadata = this . agentMetadata [ element . agent . id ] || [ ]
67+ const savedMetadata = this . agentWatchers [ element . agent . id ] ?. metadata || [ ]
6668return Promise . resolve ( savedMetadata . map ( ( metadata ) => new AgentMetadataTreeItem ( metadata ) ) )
6769}
6870
@@ -71,30 +73,39 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeIte
7173return Promise . resolve ( this . workspaces )
7274}
7375
74- async monitorMetadata ( agentId :WorkspaceAgent [ "id" ] ) :Promise < void > {
76+ // monitorMetadata opens an SSE endpoint to monitor metadata on the specified
77+ // agent and registers a disposer that can be used to stop the watch.
78+ monitorMetadata ( agentId :WorkspaceAgent [ "id" ] , token :string ) :void {
7579const agentMetadataURL = new URL ( `${ this . storage . getURL ( ) } /api/v2/workspaceagents/${ agentId } /watch-metadata` )
7680const agentMetadataEventSource = new EventSource ( agentMetadataURL . toString ( ) , {
7781headers :{
78- "Coder-Session-Token" :await this . storage . getSessionToken ( ) ,
82+ "Coder-Session-Token" :token ,
7983} ,
8084} )
8185
86+ this . agentWatchers [ agentId ] = {
87+ dispose :( ) => {
88+ delete this . agentWatchers [ agentId ]
89+ agentMetadataEventSource . close ( )
90+ } ,
91+ }
92+
8293agentMetadataEventSource . addEventListener ( "data" , ( event ) => {
8394try {
8495const dataEvent = JSON . parse ( event . data )
8596const agentMetadata = AgentMetadataEventSchemaArray . parse ( dataEvent )
8697
8798if ( agentMetadata . length === 0 ) {
88- agentMetadataEventSource . close ( )
99+ this . agentWatchers [ agentId ] . dispose ( )
89100}
90101
91- const savedMetadata = this . agentMetadata [ agentId ]
102+ const savedMetadata = this . agentWatchers [ agentId ] . metadata
92103if ( JSON . stringify ( savedMetadata ) !== JSON . stringify ( agentMetadata ) ) {
93- this . agentMetadata [ agentId ] = agentMetadata // overwrite existing metadata
104+ this . agentWatchers [ agentId ] . metadata = agentMetadata // overwrite existing metadata
94105this . refresh ( )
95106}
96107} catch ( error ) {
97- agentMetadataEventSource . close ( )
108+ this . agentWatchers [ agentId ] . dispose ( )
98109}
99110} )
100111}