Skip to content

Commit 70dfe53

Browse files
authored
VSCode: Add logic for configuring the databricks project settings (#20)
- read profiles from `~/.databrickscfg` - store project settings on `<PROJECT>/.databricks/project.json` - add cluster panel to consume the settings
1 parent 629b342 commit 70dfe53

File tree

14 files changed

+1285
-48
lines changed

14 files changed

+1285
-48
lines changed

packages/databricks-sdk-js/src/auth/CredentialProvider.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

packages/databricks-vscode/package.json

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,118 @@
3636
"url": "https://github.com/databricks/databricks-vscode.git"
3737
},
3838
"activationEvents": [
39-
"onCommand:databricks.helloWorld"
39+
"onCommand:databricks.hello",
40+
"onCommand:databricks.login",
41+
"onCommand:databricks.logout",
42+
"onCommand:databricks.configureProject",
43+
"onCommand:databricks.openDatabricksConfigFile",
44+
"onCommand:databricks.cluster.refresh",
45+
"onCommand:databricks.cluster.filterByAll",
46+
"onCommand:databricks.cluster.filterByMe",
47+
"onCommand:databricks.cluster.filterByRunning",
48+
"onView:clusterManager",
49+
"onView:clusterList"
4050
],
4151
"main": "./dist/extension.js",
4252
"contributes": {
4353
"commands": [
4454
{
45-
"command": "databricks.helloWorld",
46-
"title": "Hello World"
55+
"command": "databricks.hello",
56+
"title": "Databricks: Hello"
57+
},
58+
{
59+
"command": "databricks.login",
60+
"title": "Databricks: Login"
61+
},
62+
{
63+
"command": "databricks.logout",
64+
"title": "Databricks: Logout"
65+
},
66+
{
67+
"command": "databricks.configureProject",
68+
"icon": "$(gear)",
69+
"title": "Databricks: Configure project"
70+
},
71+
{
72+
"command": "databricks.openDatabricksConfigFile",
73+
"title": "Databricks: Open Databricks configuration file"
74+
},
75+
{
76+
"command": "databricks.cluster.filterByAll",
77+
"title": "All"
78+
},
79+
{
80+
"command": "databricks.cluster.filterByRunning",
81+
"title": "Running"
82+
},
83+
{
84+
"command": "databricks.cluster.filterByMe",
85+
"title": "Created by me"
86+
},
87+
{
88+
"command": "databricks.cluster.refresh",
89+
"icon": "$(refresh)",
90+
"title": "Refresh"
91+
}
92+
],
93+
"viewsContainers": {
94+
"activitybar": [
95+
{
96+
"id": "clusterManager",
97+
"title": "Databricks Clusters",
98+
"icon": "resources/light/logo.svg"
99+
}
100+
]
101+
},
102+
"views": {
103+
"clusterManager": [
104+
{
105+
"id": "clusterList",
106+
"name": "Clusters"
107+
}
108+
]
109+
},
110+
"viewsWelcome": [
111+
{
112+
"view": "clusterList",
113+
"contents": "In order to connect to a cluster you first have to configure your Databricks workspace:\n[Configure Databricks](command:databricks.configureProject)\nTo learn more about how to use Databricks with VS Code [read our docs](https://github.com/databricks/databricks-vscode)."
114+
}
115+
],
116+
"menus": {
117+
"view/title": [
118+
{
119+
"when": "view == clusterList",
120+
"group": "navigation@1",
121+
"submenu": "databricks.cluster.filter"
122+
},
123+
{
124+
"command": "databricks.cluster.refresh",
125+
"when": "view == clusterList",
126+
"group": "navigation@2"
127+
},
128+
{
129+
"command": "databricks.configureProject",
130+
"when": "view == clusterList",
131+
"group": "navigation@3"
132+
}
133+
],
134+
"databricks.cluster.filter": [
135+
{
136+
"command": "databricks.cluster.filterByAll"
137+
},
138+
{
139+
"command": "databricks.cluster.filterByMe"
140+
},
141+
{
142+
"command": "databricks.cluster.filterByRunning"
143+
}
144+
]
145+
},
146+
"submenus": [
147+
{
148+
"id": "databricks.cluster.filter",
149+
"label": "Filter clusters ...",
150+
"icon": "$(filter)"
47151
}
48152
]
49153
},

packages/databricks-vscode/resources/dark/logo.svg

Lines changed: 1 addition & 0 deletions
Loading

packages/databricks-vscode/resources/light/logo.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {execFile, ExecFileException, spawn} from "child_process";
2+
3+
/**
4+
* Entrypoint for all wrapped CLI commands
5+
*/
6+
export class CliWrapper {
7+
constructor() {}
8+
9+
async addProfile(
10+
name: string,
11+
host: URL,
12+
token: string
13+
): Promise<{stdout: string; stderr: string}> {
14+
return new Promise((resolve, reject) => {
15+
let child = spawn(
16+
"databricks",
17+
[
18+
"configure",
19+
"--profile",
20+
name,
21+
"--host",
22+
host.href,
23+
"--token",
24+
],
25+
{
26+
stdio: ["pipe", 0, 0],
27+
}
28+
);
29+
30+
child.stdin!.write(token);
31+
child.stdin!.end();
32+
33+
child.on("error", reject);
34+
child.on("exit", resolve);
35+
});
36+
}
37+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {ClusterFilter, ClusterModel} from "./ClusterModel";
2+
3+
/**
4+
* Cluster related commands
5+
*/
6+
export class ClusterCommands {
7+
constructor(private clusterModel: ClusterModel) {}
8+
9+
/**
10+
* Refresh cluster tree view by reloading them throug the API
11+
*/
12+
refreshCommand() {
13+
return () => {
14+
this.clusterModel.refresh();
15+
};
16+
}
17+
18+
/**
19+
* Command to filter clusters in the cluster tree view
20+
*/
21+
filterCommand(filter: ClusterFilter) {
22+
return () => {
23+
this.clusterModel.filter = filter;
24+
};
25+
}
26+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import {
2+
Disposable,
3+
Event,
4+
EventEmitter,
5+
ProviderResult,
6+
ThemeIcon,
7+
TreeDataProvider,
8+
TreeItem,
9+
TreeItemCollapsibleState,
10+
} from "vscode";
11+
import {ClusterModel, ClusterNode} from "./ClusterModel";
12+
13+
/**
14+
* Data provider for the cluster tree view
15+
*/
16+
export class ClusterListDataProvider
17+
implements TreeDataProvider<ClusterNode | TreeItem>, Disposable
18+
{
19+
private _onDidChangeTreeData: EventEmitter<
20+
ClusterNode | TreeItem | undefined | void
21+
> = new EventEmitter<ClusterNode | TreeItem | undefined | void>();
22+
readonly onDidChangeTreeData: Event<
23+
ClusterNode | TreeItem | undefined | void
24+
> = this._onDidChangeTreeData.event;
25+
26+
private disposables: Array<Disposable>;
27+
28+
constructor(private model: ClusterModel) {
29+
this.disposables = [
30+
model.onDidChange(() => {
31+
this._onDidChangeTreeData.fire();
32+
}),
33+
this.autoReload(5000),
34+
];
35+
}
36+
37+
dispose() {
38+
this.disposables.forEach((d) => d.dispose());
39+
}
40+
41+
getTreeItem(element: ClusterNode | TreeItem): TreeItem {
42+
if (!this.isClusterNode(element)) {
43+
return element;
44+
}
45+
46+
let icon: ThemeIcon;
47+
switch (element.state) {
48+
case "RUNNING":
49+
icon = new ThemeIcon("debug-start");
50+
break;
51+
52+
case "RESTARTING":
53+
case "PENDING":
54+
case "RESIZING":
55+
icon = new ThemeIcon("debug-restart");
56+
break;
57+
58+
case "TERMINATING":
59+
case "TERMINATED":
60+
case "ERROR":
61+
case "UNKNOWN":
62+
icon = new ThemeIcon("debug-stop");
63+
break;
64+
}
65+
66+
return {
67+
label: element.name,
68+
iconPath: icon,
69+
//description: `${formatSize(element.memoryMb)} MB | ${element.cores} Cores | ${element.sparkVersion}`,
70+
id: element.id,
71+
collapsibleState: TreeItemCollapsibleState.Collapsed,
72+
contextValue:
73+
element.state === "RUNNING"
74+
? "clusterRunning"
75+
: "clusterStopped",
76+
};
77+
}
78+
79+
private isClusterNode(
80+
element: ClusterNode | TreeItem
81+
): element is ClusterNode {
82+
return (element as ClusterNode).state !== undefined;
83+
}
84+
85+
private autoReload(refreshRateInMs: number): Disposable {
86+
let interval = setInterval(() => {
87+
this.model.refresh();
88+
}, refreshRateInMs);
89+
return {
90+
dispose() {
91+
clearInterval(interval);
92+
},
93+
};
94+
}
95+
96+
getChildren(
97+
element?: ClusterNode | TreeItem | undefined
98+
): ProviderResult<Array<ClusterNode | TreeItem>> {
99+
if (element) {
100+
if (this.isClusterNode(element)) {
101+
let children = [
102+
{
103+
label: "Cluster ID:",
104+
description: element.id,
105+
},
106+
];
107+
if (element.cores) {
108+
children.push({
109+
label: "Cores:",
110+
description: element.cores + "",
111+
});
112+
}
113+
if (element.memoryMb) {
114+
children.push({
115+
label: "Memory:",
116+
description: formatSize(element.memoryMb),
117+
});
118+
}
119+
children.push(
120+
{
121+
label: "Spark version:",
122+
description: element.sparkVersion,
123+
},
124+
{
125+
label: "State:",
126+
description: element.state,
127+
},
128+
{
129+
label: "Creator:",
130+
description: element.creator,
131+
}
132+
);
133+
134+
if (element.stateMessage) {
135+
children.push({
136+
label: "State message:",
137+
description: element.stateMessage,
138+
});
139+
}
140+
141+
return children;
142+
} else {
143+
return [];
144+
}
145+
} else {
146+
return (async () => {
147+
let roots = await this.model.roots;
148+
if (roots && roots.length === 0) {
149+
return [new TreeItem("No clusters found")];
150+
} else {
151+
return roots;
152+
}
153+
})();
154+
}
155+
156+
function formatSize(sizeInMB: number): string {
157+
if (sizeInMB > 1024) {
158+
return Math.round(sizeInMB / 1024).toString() + " GB";
159+
} else {
160+
return `${sizeInMB} MB`;
161+
}
162+
}
163+
}
164+
}

0 commit comments

Comments
 (0)