Skip to content

Refresh workspaces when logging in and out #124

New issue

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

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

Already on GitHub? # to your account

Merged
merged 4 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -122,6 +123,7 @@ export class Commands {
vscode.commands.executeCommand("coder.login")
}
})
vscode.commands.executeCommand("coder.refreshWorkspaces")
}

public async createWorkspace(): Promise<void> {
Expand Down
4 changes: 2 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
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
Expand Down
69 changes: 40 additions & 29 deletions src/workspacesProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,40 @@ export enum WorkspaceQuery {

export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
private workspaces: WorkspaceTreeItem[] = []
private agentMetadata: Record<WorkspaceAgent["id"], AgentMetadataEvent[]> = {}
private agentWatchers: Record<WorkspaceAgent["id"], { dispose: () => void; metadata?: AgentMetadataEvent[] }> = {}

constructor(private readonly getWorkspacesQuery: WorkspaceQuery, private readonly storage: Storage) {
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 token = await this.storage.getSessionToken()
const workspacesTreeItem: WorkspaceTreeItem[] = []
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)
})
}
this.workspaces = workspacesTreeItem
this.refresh()
}

private _onDidChangeTreeData: vscode.EventEmitter<vscode.TreeItem | undefined | null | void> =
new vscode.EventEmitter<vscode.TreeItem | undefined | null | void>()
readonly onDidChangeTreeData: vscode.Event<vscode.TreeItem | undefined | null | void> =
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)
}
Expand All @@ -62,7 +64,7 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeIte
)
return Promise.resolve(agentTreeItems)
} else if (element instanceof AgentTreeItem) {
const savedMetadata = this.agentMetadata[element.agent.id] || []
const savedMetadata = this.agentWatchers[element.agent.id]?.metadata || []
return Promise.resolve(savedMetadata.map((metadata) => new AgentMetadataTreeItem(metadata)))
}

Expand All @@ -71,30 +73,39 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeIte
return Promise.resolve(this.workspaces)
}

async monitorMetadata(agentId: WorkspaceAgent["id"]): Promise<void> {
// 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()
}
})
}
Expand Down