Skip to content

Prevent parallel refreshes #202

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 1, 2024
Merged
Changes from all commits
Commits
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
74 changes: 56 additions & 18 deletions src/workspacesProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum WorkspaceQuery {
export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
private workspaces: WorkspaceTreeItem[] = []
private agentWatchers: Record<WorkspaceAgent["id"], { dispose: () => void; metadata?: AgentMetadataEvent[] }> = {}
private fetching = false

constructor(
private readonly getWorkspacesQuery: WorkspaceQuery,
Expand All @@ -22,26 +23,63 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeIte
this.fetchAndRefresh()
}

// fetchAndRefrehsh fetches new workspaces then re-renders the entire tree.
// fetchAndRefresh fetches new workspaces then re-renders the entire tree.
// Trying to call this while already refreshing is a no-op and will return
// immediately.
async fetchAndRefresh() {
const token = await this.storage.getSessionToken()
const workspacesTreeItem: WorkspaceTreeItem[] = []
if (this.fetching) {
return
}
this.fetching = true

Object.values(this.agentWatchers).forEach((watcher) => 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 && token) {
const agents = extractAgents(workspace)
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)
})

try {
this.workspaces = await this.fetch()
} catch (error) {
this.workspaces = []
}
this.workspaces = workspacesTreeItem

this.refresh()
this.fetching = false
}

/**
* Fetch workspaces and turn them into tree items. Throw an error if not
* logged in or the query fails.
*/
async fetch(): Promise<WorkspaceTreeItem[]> {
// Assume that no URL or no token means we are not logged in.
const url = this.storage.getURL()
const token = await this.storage.getSessionToken()
if (!url || !token) {
throw new Error("not logged in")
}

const resp = await getWorkspaces({ q: this.getWorkspacesQuery })

// We could have logged out while waiting for the query, or logged into a
// different deployment.
const url2 = this.storage.getURL()
const token2 = await this.storage.getSessionToken()
if (!url2 || !token2) {
throw new Error("not logged in")
} else if (url !== url2) {
// In this case we need to fetch from the new deployment instead.
// TODO: It would be better to cancel this fetch when that happens,
// because this means we have to wait for the old fetch to finish before
// finally getting workspaces for the new one.
return this.fetch()
}

return resp.workspaces.map((workspace) => {
const showMetadata = this.getWorkspacesQuery === WorkspaceQuery.Mine
if (showMetadata) {
const agents = extractAgents(workspace)
agents.forEach((agent) => this.monitorMetadata(agent.id, url, token2)) // monitor metadata for all agents
}
return new WorkspaceTreeItem(workspace, this.getWorkspacesQuery === WorkspaceQuery.All, showMetadata)
})
}

private _onDidChangeTreeData: vscode.EventEmitter<vscode.TreeItem | undefined | null | void> =
Expand Down Expand Up @@ -78,8 +116,8 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeIte

// monitorMetadata opens an SSE endpoint 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`)
monitorMetadata(agentId: WorkspaceAgent["id"], url: string, token: string): void {
const agentMetadataURL = new URL(`${url}/api/v2/workspaceagents/${agentId}/watch-metadata`)
const agentMetadataEventSource = new EventSource(agentMetadataURL.toString(), {
headers: {
"Coder-Session-Token": token,
Expand Down