Skip to content

Commit 2d692a3

Browse files
committed
feat: implement Phase 3 - Tree View UI for kernel configurations
Add VS Code tree view interface for managing Deepnote kernel configurations. Changes: - Created DeepnoteConfigurationTreeItem with status-based icons and context values - Created DeepnoteConfigurationTreeDataProvider implementing TreeDataProvider - Created DeepnoteConfigurationsView handling all UI commands: * create: Multi-step wizard for new configurations * start/stop/restart: Server lifecycle management * delete: Configuration removal with confirmation * editName: Rename configurations * managePackages: Update package lists * refresh: Manual tree refresh - Created DeepnoteConfigurationsActivationService for initialization - Registered all services in serviceRegistry.node.ts - Added deepnoteKernelConfigurations view to package.json - Added 8 commands with icons and context menus - Added view/title and view/item/context menu contributions Technical details: - Uses Python API to enumerate available interpreters - Implements progress notifications for long-running operations - Provides input validation for names and package lists - Shows status indicators (Running/Starting/Stopped) with appropriate colors - Displays configuration details (Python path, venv, packages, timestamps) Fixes: - Made DeepnoteConfigurationManager.initialize() public to match interface - Removed unused getDisplayName() method from DeepnoteToolkitInstaller - Added type annotations to all lambda parameters
1 parent a8464f4 commit 2d692a3

8 files changed

+892
-7
lines changed

package.json

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,53 @@
8585
"category": "Deepnote",
8686
"icon": "$(reveal)"
8787
},
88+
{
89+
"command": "deepnote.configurations.create",
90+
"title": "Create Kernel Configuration",
91+
"category": "Deepnote",
92+
"icon": "$(add)"
93+
},
94+
{
95+
"command": "deepnote.configurations.start",
96+
"title": "Start Server",
97+
"category": "Deepnote",
98+
"icon": "$(debug-start)"
99+
},
100+
{
101+
"command": "deepnote.configurations.stop",
102+
"title": "Stop Server",
103+
"category": "Deepnote",
104+
"icon": "$(debug-stop)"
105+
},
106+
{
107+
"command": "deepnote.configurations.restart",
108+
"title": "Restart Server",
109+
"category": "Deepnote",
110+
"icon": "$(debug-restart)"
111+
},
112+
{
113+
"command": "deepnote.configurations.delete",
114+
"title": "Delete Configuration",
115+
"category": "Deepnote",
116+
"icon": "$(trash)"
117+
},
118+
{
119+
"command": "deepnote.configurations.managePackages",
120+
"title": "Manage Packages",
121+
"category": "Deepnote",
122+
"icon": "$(package)"
123+
},
124+
{
125+
"command": "deepnote.configurations.editName",
126+
"title": "Rename Configuration",
127+
"category": "Deepnote"
128+
},
129+
{
130+
"command": "deepnote.configurations.refresh",
131+
"title": "Refresh",
132+
"category": "Deepnote",
133+
"icon": "$(refresh)"
134+
},
88135
{
89136
"command": "dataScience.ClearCache",
90137
"title": "%jupyter.command.dataScience.clearCache.title%",
@@ -1226,11 +1273,53 @@
12261273
"when": "resourceLangId == python && !isInDiffEditor && isWorkspaceTrusted"
12271274
}
12281275
],
1276+
"view/title": [
1277+
{
1278+
"command": "deepnote.configurations.create",
1279+
"when": "view == deepnoteKernelConfigurations",
1280+
"group": "navigation@1"
1281+
},
1282+
{
1283+
"command": "deepnote.configurations.refresh",
1284+
"when": "view == deepnoteKernelConfigurations",
1285+
"group": "navigation@2"
1286+
}
1287+
],
12291288
"view/item/context": [
12301289
{
12311290
"command": "deepnote.revealInExplorer",
12321291
"when": "view == deepnoteExplorer",
12331292
"group": "inline@2"
1293+
},
1294+
{
1295+
"command": "deepnote.configurations.start",
1296+
"when": "view == deepnoteKernelConfigurations && viewItem == deepnoteConfiguration.stopped",
1297+
"group": "inline@1"
1298+
},
1299+
{
1300+
"command": "deepnote.configurations.stop",
1301+
"when": "view == deepnoteKernelConfigurations && viewItem == deepnoteConfiguration.running",
1302+
"group": "inline@1"
1303+
},
1304+
{
1305+
"command": "deepnote.configurations.restart",
1306+
"when": "view == deepnoteKernelConfigurations && viewItem == deepnoteConfiguration.running",
1307+
"group": "1_lifecycle@1"
1308+
},
1309+
{
1310+
"command": "deepnote.configurations.managePackages",
1311+
"when": "view == deepnoteKernelConfigurations && viewItem =~ /deepnoteConfiguration\\.(running|stopped)/",
1312+
"group": "2_manage@1"
1313+
},
1314+
{
1315+
"command": "deepnote.configurations.editName",
1316+
"when": "view == deepnoteKernelConfigurations && viewItem =~ /deepnoteConfiguration\\.(running|stopped)/",
1317+
"group": "2_manage@2"
1318+
},
1319+
{
1320+
"command": "deepnote.configurations.delete",
1321+
"when": "view == deepnoteKernelConfigurations && viewItem =~ /deepnoteConfiguration\\.(running|stopped)/",
1322+
"group": "4_danger@1"
12341323
}
12351324
]
12361325
},
@@ -1836,6 +1925,11 @@
18361925
"light": "./resources/light/deepnote-icon.svg",
18371926
"dark": "./resources/dark/deepnote-icon.svg"
18381927
}
1928+
},
1929+
{
1930+
"id": "deepnoteKernelConfigurations",
1931+
"name": "Kernel Configurations",
1932+
"when": "workspaceFolderCount != 0"
18391933
}
18401934
]
18411935
},

src/kernels/deepnote/configurations/deepnoteConfigurationManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class DeepnoteConfigurationManager implements IExtensionSyncActivationSer
4242
/**
4343
* Initialize the manager by loading configurations from storage
4444
*/
45-
private async initialize(): Promise<void> {
45+
public async initialize(): Promise<void> {
4646
try {
4747
const configs = await this.storage.loadConfigurations();
4848
this.configurations.clear();
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { Event, EventEmitter, TreeDataProvider, TreeItem } from 'vscode';
5+
import { IDeepnoteConfigurationManager } from '../types';
6+
import { ConfigurationTreeItemType, DeepnoteConfigurationTreeItem } from './deepnoteConfigurationTreeItem';
7+
import { KernelConfigurationStatus } from './deepnoteKernelConfiguration';
8+
9+
/**
10+
* Tree data provider for the Deepnote kernel configurations view
11+
*/
12+
export class DeepnoteConfigurationTreeDataProvider implements TreeDataProvider<DeepnoteConfigurationTreeItem> {
13+
private readonly _onDidChangeTreeData = new EventEmitter<DeepnoteConfigurationTreeItem | undefined | void>();
14+
public readonly onDidChangeTreeData: Event<DeepnoteConfigurationTreeItem | undefined | void> =
15+
this._onDidChangeTreeData.event;
16+
17+
constructor(private readonly configurationManager: IDeepnoteConfigurationManager) {
18+
// Listen to configuration changes and refresh the tree
19+
this.configurationManager.onDidChangeConfigurations(() => {
20+
this.refresh();
21+
});
22+
}
23+
24+
public refresh(): void {
25+
this._onDidChangeTreeData.fire();
26+
}
27+
28+
public getTreeItem(element: DeepnoteConfigurationTreeItem): TreeItem {
29+
return element;
30+
}
31+
32+
public async getChildren(element?: DeepnoteConfigurationTreeItem): Promise<DeepnoteConfigurationTreeItem[]> {
33+
if (!element) {
34+
// Root level: show all configurations + create action
35+
return this.getRootItems();
36+
}
37+
38+
// Expanded configuration: show info items
39+
if (element.type === ConfigurationTreeItemType.Configuration && element.configuration) {
40+
return this.getConfigurationInfoItems(element);
41+
}
42+
43+
return [];
44+
}
45+
46+
private async getRootItems(): Promise<DeepnoteConfigurationTreeItem[]> {
47+
const configurations = this.configurationManager.listConfigurations();
48+
const items: DeepnoteConfigurationTreeItem[] = [];
49+
50+
// Add configuration items
51+
for (const config of configurations) {
52+
const statusInfo = this.configurationManager.getConfigurationWithStatus(config.id);
53+
const status = statusInfo?.status || KernelConfigurationStatus.Stopped;
54+
55+
const item = new DeepnoteConfigurationTreeItem(ConfigurationTreeItemType.Configuration, config, status);
56+
57+
items.push(item);
58+
}
59+
60+
// Add create action at the end
61+
items.push(new DeepnoteConfigurationTreeItem(ConfigurationTreeItemType.CreateAction));
62+
63+
return items;
64+
}
65+
66+
private getConfigurationInfoItems(element: DeepnoteConfigurationTreeItem): DeepnoteConfigurationTreeItem[] {
67+
const config = element.configuration;
68+
if (!config) {
69+
return [];
70+
}
71+
72+
const items: DeepnoteConfigurationTreeItem[] = [];
73+
const statusInfo = this.configurationManager.getConfigurationWithStatus(config.id);
74+
75+
// Server status and port
76+
if (statusInfo?.status === KernelConfigurationStatus.Running && config.serverInfo) {
77+
items.push(DeepnoteConfigurationTreeItem.createInfoItem(`Port: ${config.serverInfo.port}`, 'port'));
78+
items.push(DeepnoteConfigurationTreeItem.createInfoItem(`URL: ${config.serverInfo.url}`, 'globe'));
79+
}
80+
81+
// Python interpreter
82+
items.push(
83+
DeepnoteConfigurationTreeItem.createInfoItem(
84+
`Python: ${this.getShortPath(config.pythonInterpreter.uri.fsPath)}`,
85+
'symbol-namespace'
86+
)
87+
);
88+
89+
// Venv path
90+
items.push(
91+
DeepnoteConfigurationTreeItem.createInfoItem(`Venv: ${this.getShortPath(config.venvPath.fsPath)}`, 'folder')
92+
);
93+
94+
// Packages
95+
if (config.packages && config.packages.length > 0) {
96+
items.push(
97+
DeepnoteConfigurationTreeItem.createInfoItem(`Packages: ${config.packages.join(', ')}`, 'package')
98+
);
99+
} else {
100+
items.push(DeepnoteConfigurationTreeItem.createInfoItem('Packages: (none)', 'package'));
101+
}
102+
103+
// Toolkit version
104+
if (config.toolkitVersion) {
105+
items.push(DeepnoteConfigurationTreeItem.createInfoItem(`Toolkit: ${config.toolkitVersion}`, 'versions'));
106+
}
107+
108+
// Timestamps
109+
items.push(
110+
DeepnoteConfigurationTreeItem.createInfoItem(`Created: ${config.createdAt.toLocaleString()}`, 'history')
111+
);
112+
113+
items.push(
114+
DeepnoteConfigurationTreeItem.createInfoItem(`Last used: ${config.lastUsedAt.toLocaleString()}`, 'clock')
115+
);
116+
117+
return items;
118+
}
119+
120+
/**
121+
* Shorten a file path for display (show last 2-3 segments)
122+
*/
123+
private getShortPath(fullPath: string): string {
124+
const parts = fullPath.split(/[/\\]/);
125+
if (parts.length <= 3) {
126+
return fullPath;
127+
}
128+
129+
// Show last 3 segments with ellipsis
130+
return `.../${parts.slice(-3).join('/')}`;
131+
}
132+
133+
public dispose(): void {
134+
this._onDidChangeTreeData.dispose();
135+
}
136+
}

0 commit comments

Comments
 (0)