Skip to content

Commit e40d876

Browse files
committed
chore: updating sidebar to also support multiple agents
1 parent 24d16b8 commit e40d876

File tree

5 files changed

+136
-90
lines changed

5 files changed

+136
-90
lines changed

package.json

+10-5
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@
9494
"title": "Coder: Open Workspace",
9595
"icon": "$(play)"
9696
},
97+
{
98+
"command": "coder.openFromSidebar",
99+
"title": "Coder: Open Workspace",
100+
"icon": "$(play)"
101+
},
97102
{
98103
"command": "coder.createWorkspace",
99104
"title": "Create Workspace",
@@ -147,18 +152,18 @@
147152
],
148153
"view/item/context": [
149154
{
150-
"command": "coder.open",
151-
"when": "coder.authenticated && view == myWorkspaces || coder.authenticated && view == allWorkspaces",
155+
"command": "coder.openFromSidebar",
156+
"when": "coder.authenticated && viewItem == coderWorkspaceSingleAgent || coder.authenticated && viewItem == coderAgent",
152157
"group": "inline"
153158
},
154159
{
155160
"command": "coder.navigateToWorkspace",
156-
"when": "coder.authenticated && view == myWorkspaces || coder.authenticated && view == allWorkspaces",
161+
"when": "coder.authenticated && viewItem == coderWorkspaceSingleAgent || coder.authenticated && viewItem == coderWorkspaceMultipleAgents",
157162
"group": "inline"
158163
},
159164
{
160165
"command": "coder.navigateToWorkspaceSettings",
161-
"when": "coder.authenticated && view == myWorkspaces || coder.authenticated && view == allWorkspaces",
166+
"when": "coder.authenticated && viewItem == coderWorkspaceSingleAgent || coder.authenticated && viewItem == coderWorkspaceMultipleAgents",
162167
"group": "inline"
163168
}
164169
]
@@ -223,4 +228,4 @@
223228
"ws": "^8.11.0",
224229
"yaml": "^1.10.0"
225230
}
226-
}
231+
}

src/api-helper.ts

+2-9
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
import { Workspace, WorkspaceAgent } from "coder/site/src/api/typesGenerated"
22

3-
export function extractAgentsAndFolderPath(
4-
workspace: Workspace,
5-
): [agents: WorkspaceAgent[], folderPath: string | undefined] {
6-
// TODO: multiple agent support
3+
export function extractAgents(workspace: Workspace): WorkspaceAgent[] {
74
const agents = workspace.latest_build.resources.reduce((acc, resource) => {
85
return acc.concat(resource.agents || [])
96
}, [] as WorkspaceAgent[])
107

11-
let folderPath = undefined
12-
if (agents.length === 1) {
13-
folderPath = agents[0].expanded_directory
14-
}
15-
return [agents, folderPath]
8+
return agents
169
}

src/commands.ts

+85-71
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,17 @@ export class Commands {
143143
}
144144
}
145145

146+
public async openFromSidebar(treeItem: WorkspaceTreeItem) {
147+
if (treeItem) {
148+
await openWorkspace(
149+
treeItem.workspaceOwner,
150+
treeItem.workspaceName,
151+
treeItem.workspaceAgent,
152+
treeItem.workspaceFolderPath,
153+
)
154+
}
155+
}
156+
146157
public async open(...args: unknown[]): Promise<void> {
147158
let workspaceOwner: string
148159
let workspaceName: string
@@ -248,84 +259,14 @@ export class Commands {
248259
workspaceAgent = ""
249260
}
250261
}
251-
} else if (args.length === 2) {
252-
// opening a workspace from the sidebar
253-
const workspaceTreeItem = args[0] as WorkspaceTreeItem
254-
workspaceOwner = workspaceTreeItem.workspaceOwner
255-
workspaceName = workspaceTreeItem.workspaceName
256-
folderPath = workspaceTreeItem.workspaceFolderPath
257262
} else {
258263
workspaceOwner = args[0] as string
259264
workspaceName = args[1] as string
260265
// workspaceAgent is reserved for args[2], but multiple agents aren't supported yet.
261266
folderPath = args[3] as string | undefined
262267
}
263268

264-
// A workspace can have multiple agents, but that's handled
265-
// when opening a workspace unless explicitly specified.
266-
let remoteAuthority = `ssh-remote+${Remote.Prefix}${workspaceOwner}--${workspaceName}`
267-
if (workspaceAgent) {
268-
remoteAuthority += `--${workspaceAgent}`
269-
}
270-
271-
let newWindow = true
272-
// Open in the existing window if no workspaces are open.
273-
if (!vscode.workspace.workspaceFolders?.length) {
274-
newWindow = false
275-
}
276-
277-
// If a folder isn't specified, we can try to open a recently opened folder.
278-
if (!folderPath) {
279-
const output: {
280-
workspaces: { folderUri: vscode.Uri; remoteAuthority: string }[]
281-
} = await vscode.commands.executeCommand("_workbench.getRecentlyOpened")
282-
const opened = output.workspaces.filter(
283-
// Filter out `/` since that's added below.
284-
(opened) => opened.folderUri?.authority === remoteAuthority,
285-
)
286-
if (opened.length > 0) {
287-
let selected: (typeof opened)[0]
288-
289-
if (opened.length > 1) {
290-
const items: vscode.QuickPickItem[] = opened.map((folder): vscode.QuickPickItem => {
291-
return {
292-
label: folder.folderUri.path,
293-
}
294-
})
295-
const item = await vscode.window.showQuickPick(items, {
296-
title: "Select a recently opened folder",
297-
})
298-
if (!item) {
299-
return
300-
}
301-
selected = opened[items.indexOf(item)]
302-
} else {
303-
selected = opened[0]
304-
}
305-
306-
folderPath = selected.folderUri.path
307-
}
308-
}
309-
310-
if (folderPath) {
311-
await vscode.commands.executeCommand(
312-
"vscode.openFolder",
313-
vscode.Uri.from({
314-
scheme: "vscode-remote",
315-
authority: remoteAuthority,
316-
path: folderPath,
317-
}),
318-
// Open this in a new window!
319-
newWindow,
320-
)
321-
return
322-
}
323-
324-
// This opens the workspace without an active folder opened.
325-
await vscode.commands.executeCommand("vscode.newWindow", {
326-
remoteAuthority: remoteAuthority,
327-
reuseWindow: !newWindow,
328-
})
269+
await openWorkspace(workspaceOwner, workspaceName, workspaceAgent, folderPath)
329270
}
330271

331272
public async updateWorkspace(): Promise<void> {
@@ -346,3 +287,76 @@ export class Commands {
346287
}
347288
}
348289
}
290+
291+
async function openWorkspace(
292+
workspaceOwner: string,
293+
workspaceName: string,
294+
workspaceAgent: string | undefined,
295+
folderPath: string | undefined,
296+
) {
297+
// A workspace can have multiple agents, but that's handled
298+
// when opening a workspace unless explicitly specified.
299+
let remoteAuthority = `ssh-remote+${Remote.Prefix}${workspaceOwner}--${workspaceName}`
300+
if (workspaceAgent) {
301+
remoteAuthority += `--${workspaceAgent}`
302+
}
303+
304+
let newWindow = true
305+
// Open in the existing window if no workspaces are open.
306+
if (!vscode.workspace.workspaceFolders?.length) {
307+
newWindow = false
308+
}
309+
310+
// If a folder isn't specified, we can try to open a recently opened folder.
311+
if (!folderPath) {
312+
const output: {
313+
workspaces: { folderUri: vscode.Uri; remoteAuthority: string }[]
314+
} = await vscode.commands.executeCommand("_workbench.getRecentlyOpened")
315+
const opened = output.workspaces.filter(
316+
// Filter out `/` since that's added below.
317+
(opened) => opened.folderUri?.authority === remoteAuthority,
318+
)
319+
if (opened.length > 0) {
320+
let selected: (typeof opened)[0]
321+
322+
if (opened.length > 1) {
323+
const items: vscode.QuickPickItem[] = opened.map((folder): vscode.QuickPickItem => {
324+
return {
325+
label: folder.folderUri.path,
326+
}
327+
})
328+
const item = await vscode.window.showQuickPick(items, {
329+
title: "Select a recently opened folder",
330+
})
331+
if (!item) {
332+
return
333+
}
334+
selected = opened[items.indexOf(item)]
335+
} else {
336+
selected = opened[0]
337+
}
338+
339+
folderPath = selected.folderUri.path
340+
}
341+
}
342+
343+
if (folderPath) {
344+
await vscode.commands.executeCommand(
345+
"vscode.openFolder",
346+
vscode.Uri.from({
347+
scheme: "vscode-remote",
348+
authority: remoteAuthority,
349+
path: folderPath,
350+
}),
351+
// Open this in a new window!
352+
newWindow,
353+
)
354+
return
355+
}
356+
357+
// This opens the workspace without an active folder opened.
358+
await vscode.commands.executeCommand("vscode.newWindow", {
359+
remoteAuthority: remoteAuthority,
360+
reuseWindow: !newWindow,
361+
})
362+
}

src/extension.ts

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
8787
vscode.commands.registerCommand("coder.login", commands.login.bind(commands))
8888
vscode.commands.registerCommand("coder.logout", commands.logout.bind(commands))
8989
vscode.commands.registerCommand("coder.open", commands.open.bind(commands))
90+
vscode.commands.registerCommand("coder.openFromSidebar", commands.openFromSidebar.bind(commands))
9091
vscode.commands.registerCommand("coder.workspace.update", commands.updateWorkspace.bind(commands))
9192
vscode.commands.registerCommand("coder.createWorkspace", commands.createWorkspace.bind(commands))
9293
vscode.commands.registerCommand("coder.navigateToWorkspace", commands.navigateToWorkspace.bind(commands))

src/workspacesProvider.ts

+38-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { getWorkspaces } from "coder/site/src/api/api"
2+
import { WorkspaceAgent } from "coder/site/src/api/typesGenerated"
23
import * as path from "path"
34
import * as vscode from "vscode"
4-
import { extractAgentsAndFolderPath } from "./api-helper"
5+
import { extractAgents } from "./api-helper"
56

67
export enum WorkspaceQuery {
78
Mine = "owner:me",
@@ -24,7 +25,19 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<WorkspaceTreeI
2425
return element
2526
}
2627

27-
getChildren(): Thenable<WorkspaceTreeItem[]> {
28+
getChildren(element?: WorkspaceTreeItem): Thenable<WorkspaceTreeItem[]> {
29+
if (element) {
30+
if (element.agents.length > 0) {
31+
return Promise.resolve(
32+
element.agents.map((agent) => {
33+
const label = agent.name
34+
const detail = `Status: ${agent.status}`
35+
return new WorkspaceTreeItem(label, detail, "", "", agent.name, agent.expanded_directory, [], "coderAgent")
36+
}),
37+
)
38+
}
39+
return Promise.resolve([])
40+
}
2841
return getWorkspaces({ q: this.getWorkspacesQuery }).then((workspaces) => {
2942
return workspaces.workspaces.map((workspace) => {
3043
const status =
@@ -35,22 +48,42 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<WorkspaceTreeI
3548
? `${workspace.owner_name} / ${workspace.name}`
3649
: workspace.name
3750
const detail = `Template: ${workspace.template_display_name || workspace.template_name} • Status: ${status}`
38-
const [, folderPath] = extractAgentsAndFolderPath(workspace)
39-
return new WorkspaceTreeItem(label, detail, workspace.owner_name, workspace.name, folderPath)
51+
const agents = extractAgents(workspace)
52+
return new WorkspaceTreeItem(
53+
label,
54+
detail,
55+
workspace.owner_name,
56+
workspace.name,
57+
undefined,
58+
agents[0]?.expanded_directory,
59+
agents,
60+
agents.length > 1 ? "coderWorkspaceMultipleAgents" : "coderWorkspaceSingleAgent",
61+
)
4062
})
4163
})
4264
}
4365
}
4466

67+
type CoderTreeItemType = "coderWorkspaceSingleAgent" | "coderWorkspaceMultipleAgents" | "coderAgent"
68+
4569
export class WorkspaceTreeItem extends vscode.TreeItem {
4670
constructor(
4771
public readonly label: string,
4872
public readonly tooltip: string,
4973
public readonly workspaceOwner: string,
5074
public readonly workspaceName: string,
75+
public readonly workspaceAgent: string | undefined,
5176
public readonly workspaceFolderPath: string | undefined,
77+
public readonly agents: WorkspaceAgent[],
78+
contextValue: CoderTreeItemType,
5279
) {
53-
super(label, vscode.TreeItemCollapsibleState.None)
80+
super(
81+
label,
82+
contextValue === "coderWorkspaceMultipleAgents"
83+
? vscode.TreeItemCollapsibleState.Collapsed
84+
: vscode.TreeItemCollapsibleState.None,
85+
)
86+
this.contextValue = contextValue
5487
}
5588

5689
iconPath = {

0 commit comments

Comments
 (0)