@@ -14,6 +14,7 @@ export enum WorkspaceQuery {
14
14
export class WorkspaceProvider implements vscode . TreeDataProvider < vscode . TreeItem > {
15
15
private workspaces : WorkspaceTreeItem [ ] = [ ]
16
16
private agentWatchers : Record < WorkspaceAgent [ "id" ] , { dispose : ( ) => void ; metadata ?: AgentMetadataEvent [ ] } > = { }
17
+ private fetching = false
17
18
18
19
constructor (
19
20
private readonly getWorkspacesQuery : WorkspaceQuery ,
@@ -22,26 +23,63 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeIte
22
23
this . fetchAndRefresh ( )
23
24
}
24
25
25
- // fetchAndRefrehsh fetches new workspaces then re-renders the entire tree.
26
+ // fetchAndRefresh fetches new workspaces then re-renders the entire tree.
27
+ // Trying to call this while already refreshing is a no-op and will return
28
+ // immediately.
26
29
async fetchAndRefresh ( ) {
27
- const token = await this . storage . getSessionToken ( )
28
- const workspacesTreeItem : WorkspaceTreeItem [ ] = [ ]
30
+ if ( this . fetching ) {
31
+ return
32
+ }
33
+ this . fetching = true
34
+
29
35
Object . values ( this . agentWatchers ) . forEach ( ( watcher ) => watcher . dispose ( ) )
30
- // If the URL is set then we are logged in.
31
- if ( this . storage . getURL ( ) ) {
32
- const resp = await getWorkspaces ( { q : this . getWorkspacesQuery } )
33
- resp . workspaces . forEach ( ( workspace ) => {
34
- const showMetadata = this . getWorkspacesQuery === WorkspaceQuery . Mine
35
- if ( showMetadata && token ) {
36
- const agents = extractAgents ( workspace )
37
- agents . forEach ( ( agent ) => this . monitorMetadata ( agent . id , token ) ) // monitor metadata for all agents
38
- }
39
- const treeItem = new WorkspaceTreeItem ( workspace , this . getWorkspacesQuery === WorkspaceQuery . All , showMetadata )
40
- workspacesTreeItem . push ( treeItem )
41
- } )
36
+
37
+ try {
38
+ this . workspaces = await this . fetch ( )
39
+ } catch ( error ) {
40
+ this . workspaces = [ ]
42
41
}
43
- this . workspaces = workspacesTreeItem
42
+
44
43
this . refresh ( )
44
+ this . fetching = false
45
+ }
46
+
47
+ /**
48
+ * Fetch workspaces and turn them into tree items. Throw an error if not
49
+ * logged in or the query fails.
50
+ */
51
+ async fetch ( ) : Promise < WorkspaceTreeItem [ ] > {
52
+ // Assume that no URL or no token means we are not logged in.
53
+ const url = this . storage . getURL ( )
54
+ const token = await this . storage . getSessionToken ( )
55
+ if ( ! url || ! token ) {
56
+ throw new Error ( "not logged in" )
57
+ }
58
+
59
+ const resp = await getWorkspaces ( { q : this . getWorkspacesQuery } )
60
+
61
+ // We could have logged out while waiting for the query, or logged into a
62
+ // different deployment.
63
+ const url2 = this . storage . getURL ( )
64
+ const token2 = await this . storage . getSessionToken ( )
65
+ if ( ! url2 || ! token2 ) {
66
+ throw new Error ( "not logged in" )
67
+ } else if ( url !== url2 ) {
68
+ // In this case we need to fetch from the new deployment instead.
69
+ // TODO: It would be better to cancel this fetch when that happens,
70
+ // because this means we have to wait for the old fetch to finish before
71
+ // finally getting workspaces for the new one.
72
+ return this . fetch ( )
73
+ }
74
+
75
+ return resp . workspaces . map ( ( workspace ) => {
76
+ const showMetadata = this . getWorkspacesQuery === WorkspaceQuery . Mine
77
+ if ( showMetadata ) {
78
+ const agents = extractAgents ( workspace )
79
+ agents . forEach ( ( agent ) => this . monitorMetadata ( agent . id , url , token2 ) ) // monitor metadata for all agents
80
+ }
81
+ return new WorkspaceTreeItem ( workspace , this . getWorkspacesQuery === WorkspaceQuery . All , showMetadata )
82
+ } )
45
83
}
46
84
47
85
private _onDidChangeTreeData : vscode . EventEmitter < vscode . TreeItem | undefined | null | void > =
@@ -78,8 +116,8 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeIte
78
116
79
117
// monitorMetadata opens an SSE endpoint to monitor metadata on the specified
80
118
// agent and registers a disposer that can be used to stop the watch.
81
- monitorMetadata ( agentId : WorkspaceAgent [ "id" ] , token : string ) : void {
82
- const agentMetadataURL = new URL ( `${ this . storage . getURL ( ) } /api/v2/workspaceagents/${ agentId } /watch-metadata` )
119
+ monitorMetadata ( agentId : WorkspaceAgent [ "id" ] , url : string , token : string ) : void {
120
+ const agentMetadataURL = new URL ( `${ url } /api/v2/workspaceagents/${ agentId } /watch-metadata` )
83
121
const agentMetadataEventSource = new EventSource ( agentMetadataURL . toString ( ) , {
84
122
headers : {
85
123
"Coder-Session-Token" : token ,
0 commit comments