Skip to content

Commit 0eb9e6c

Browse files
VSCode: Add start/stop sync functionality to the configuration view (#77)
- convert "bricks sync" command into a proper VSCode task - update run configuration to use sync task as a pre-condition - add UI to manage start/stop from the configuration view fixes #64 https://user-images.githubusercontent.com/40952/188631973-ca0c8b14-e225-4155-8f9a-b805276da4f1.mov Co-authored-by: Kartik Gupta <88345179+kartikgupta-db@users.noreply.github.com>
1 parent c47067c commit 0eb9e6c

20 files changed

+558
-206
lines changed

packages/databricks-vscode/package.json

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@
4545
"onCommand:databricks.connection.detachCluster",
4646
"onCommand:databricks.connection.attachSyncDestination",
4747
"onCommand:databricks.connection.detachSyncDestination",
48-
"onCommand:databricks.cli.startSync",
49-
"onCommand:databricks.cli.startSyncFull",
50-
"onCommand:databricks.cli.stopSync",
48+
"onCommand:databricks.sync.start",
49+
"onCommand:databricks.sync.startFull",
50+
"onCommand:databricks.sync.stop",
5151
"onCommand:databricks.cli.testBricksCli",
5252
"onCommand:databricks.cluster.refresh",
5353
"onCommand:databricks.cluster.filterByAll",
@@ -56,8 +56,9 @@
5656
"onCommand:databricks.run.getProgramName",
5757
"onCommand:databricks.run.runEditorContentsAsWorkflow",
5858
"onCommand:databricks.run.runEditorContents",
59-
"onView:clusterManager",
60-
"onView:clusterList",
59+
"onView:configurationView",
60+
"onView:clusterView",
61+
"onTaskType:databricks",
6162
"onDebugResolve:databricks",
6263
"onDebugResolve:databricks-workflow"
6364
],
@@ -97,16 +98,19 @@
9798
"icon": "$(debug-disconnect)"
9899
},
99100
{
100-
"command": "databricks.cli.startSync",
101-
"title": "Databricks: Start synchronization"
101+
"command": "databricks.sync.start",
102+
"title": "Databricks: Start synchronization",
103+
"icon": "$(sync)"
102104
},
103105
{
104-
"command": "databricks.cli.startSyncFull",
105-
"title": "Databricks: Start synchronization (full sync)"
106+
"command": "databricks.sync.startFull",
107+
"title": "Databricks: Start synchronization (full sync)",
108+
"icon": "$(sync)"
106109
},
107110
{
108-
"command": "databricks.cli.stopSync",
109-
"title": "Databricks: Stop synchronization"
111+
"command": "databricks.sync.stop",
112+
"title": "Databricks: Stop synchronization",
113+
"icon": "$(sync-ignored)"
110114
},
111115
{
112116
"command": "databricks.cli.testBricksCli",
@@ -170,7 +174,7 @@
170174
"name": "Configuration"
171175
},
172176
{
173-
"id": "clusterList",
177+
"id": "clusterView",
174178
"name": "Clusters"
175179
}
176180
]
@@ -189,13 +193,13 @@
189193
"group": "navigation@3"
190194
},
191195
{
192-
"when": "view == clusterList",
196+
"when": "view == clusterView",
193197
"group": "navigation@1",
194198
"submenu": "databricks.cluster.filter"
195199
},
196200
{
197201
"command": "databricks.cluster.refresh",
198-
"when": "view == clusterList",
202+
"when": "view == clusterView",
199203
"group": "navigation@2"
200204
}
201205
],
@@ -213,7 +217,7 @@
213217
"view/item/context": [
214218
{
215219
"command": "databricks.connection.attachCluster",
216-
"when": "view == clusterList",
220+
"when": "view == clusterView",
217221
"group": "inline@0"
218222
},
219223
{
@@ -228,12 +232,22 @@
228232
},
229233
{
230234
"command": "databricks.connection.attachSyncDestination",
231-
"when": "view == configurationView && viewItem == syncDestinationDetached",
235+
"when": "view == configurationView && viewItem == syncDetached",
232236
"group": "inline@0"
233237
},
234238
{
235239
"command": "databricks.connection.detachSyncDestination",
236-
"when": "view == configurationView && viewItem == syncDestinationAttached",
240+
"when": "view == configurationView && viewItem == syncStopped || view == configurationView && viewItem == syncRunning",
241+
"group": "inline@0"
242+
},
243+
{
244+
"command": "databricks.sync.start",
245+
"when": "view == configurationView && viewItem == syncStopped",
246+
"group": "inline@0"
247+
},
248+
{
249+
"command": "databricks.sync.stop",
250+
"when": "view == configurationView && viewItem == syncRunning",
237251
"group": "inline@0"
238252
}
239253
],
@@ -267,6 +281,23 @@
267281
"icon": "$(filter)"
268282
}
269283
],
284+
"taskDefinitions": [
285+
{
286+
"type": "databricks",
287+
"required": [
288+
"task"
289+
],
290+
"properties": {
291+
"task": {
292+
"type": "string",
293+
"enum": [
294+
"sync"
295+
],
296+
"default": "sync"
297+
}
298+
}
299+
}
300+
],
270301
"problemMatchers": [
271302
{
272303
"name": "bricks-sync",
@@ -316,7 +347,8 @@
316347
"request": "launch",
317348
"name": "Run on Databricks",
318349
"program": "${file}",
319-
"args": []
350+
"args": [],
351+
"preLaunchTask": "databricks: sync"
320352
}
321353
],
322354
"configurationSnippets": [
@@ -328,7 +360,8 @@
328360
"request": "launch",
329361
"name": "Run on Databricks",
330362
"program": "^\"\\${file}\"",
331-
"args": []
363+
"args": [],
364+
"preLaunchTask": "databricks: sync"
332365
}
333366
}
334367
]
@@ -372,7 +405,9 @@
372405
"request": "launch",
373406
"name": "Run on Databricks as Workflow",
374407
"program": "${file}",
375-
"parameters": {}
408+
"parameters": {},
409+
"args": [],
410+
"preLaunchTask": "databricks: sync"
376411
}
377412
],
378413
"configurationSnippets": [
@@ -384,7 +419,9 @@
384419
"request": "launch",
385420
"name": "Run on Databricks as a Workflow",
386421
"program": "^\"\\${file}\"",
387-
"args": []
422+
"parameters": {},
423+
"args": [],
424+
"preLaunchTask": "databricks: sync"
388425
}
389426
}
390427
]
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import * as assert from "assert";
2+
import {anything, instance, mock, when, verify} from "ts-mockito";
3+
import {ProcessExecution, Uri} from "vscode";
4+
import {ConnectionManager} from "../configuration/ConnectionManager";
5+
import {SyncDestination} from "../configuration/SyncDestination";
6+
import {BricksTaskProvider, SyncTask} from "./BricksTasks";
7+
import {CliWrapper} from "./CliWrapper";
8+
9+
describe(__filename, () => {
10+
let connection: ConnectionManager;
11+
let cli: CliWrapper;
12+
13+
beforeEach(() => {
14+
connection = instance(mock(ConnectionManager));
15+
cli = instance(mock(CliWrapper));
16+
});
17+
18+
it("should create a task provider", () => {
19+
let provider = new BricksTaskProvider(connection, cli);
20+
let tasks = provider.provideTasks();
21+
22+
assert.equal(tasks.length, 1);
23+
assert.equal(tasks[0].definition.type, "databricks");
24+
assert.equal(tasks[0].definition.task, "sync");
25+
});
26+
27+
it("should create a sync task", () => {
28+
let task = new SyncTask(connection, cli, "incremental");
29+
30+
assert.equal(task.definition.type, "databricks");
31+
assert.equal(task.definition.task, "sync");
32+
assert.equal(task.isBackground, true);
33+
assert.deepEqual(task.problemMatchers, ["$bricks-sync"]);
34+
});
35+
36+
it("should lazily create a process execution", () => {
37+
let connectionMock = mock(ConnectionManager);
38+
when(connectionMock.profile).thenReturn("DEFAULT");
39+
when(connectionMock.me).thenReturn("fabian.jakobs@databricks.com");
40+
when(connectionMock.syncDestination).thenReturn(
41+
new SyncDestination(
42+
Uri.from({
43+
scheme: "dbws",
44+
path: "/Workspace/notebook-best-practices",
45+
}),
46+
Uri.file("/Desktop/notebook-best-practices")
47+
)
48+
);
49+
50+
let cliMock = mock(CliWrapper);
51+
when(
52+
cliMock.getSyncCommand(
53+
anything(),
54+
anything(),
55+
anything(),
56+
anything()
57+
)
58+
).thenReturn({
59+
command: "bricks",
60+
args: ["sync", "incremental"],
61+
});
62+
63+
let task = new SyncTask(
64+
instance(connectionMock),
65+
instance(cliMock),
66+
"incremental"
67+
);
68+
assert.ok(task.execution);
69+
70+
let execution = task.execution as ProcessExecution;
71+
72+
const syncCommandMock = cliMock.getSyncCommand(
73+
anything(),
74+
anything(),
75+
anything(),
76+
anything()
77+
);
78+
79+
verify(syncCommandMock).never();
80+
assert.equal(execution.process, "bricks");
81+
verify(syncCommandMock).once();
82+
assert.deepEqual(execution.args, ["sync", "incremental"]);
83+
verify(syncCommandMock).once();
84+
});
85+
});
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import {
2+
ProcessExecution,
3+
ProcessExecutionOptions,
4+
Task,
5+
TaskGroup,
6+
TaskProvider,
7+
TaskRevealKind,
8+
TaskScope,
9+
window,
10+
workspace,
11+
} from "vscode";
12+
import {ConnectionManager} from "../configuration/ConnectionManager";
13+
import {CliWrapper, Command} from "./CliWrapper";
14+
15+
export class BricksTaskProvider implements TaskProvider {
16+
constructor(
17+
private connection: ConnectionManager,
18+
private cli: CliWrapper
19+
) {}
20+
21+
provideTasks(): Task[] {
22+
return [new SyncTask(this.connection, this.cli, "incremental")];
23+
}
24+
resolveTask(): Task | undefined {
25+
return undefined;
26+
}
27+
}
28+
29+
export class SyncTask extends Task {
30+
constructor(
31+
connection: ConnectionManager,
32+
cli: CliWrapper,
33+
syncType: "full" | "incremental"
34+
) {
35+
super(
36+
{
37+
type: "databricks",
38+
task: "sync",
39+
},
40+
TaskScope.Workspace,
41+
"sync",
42+
"databricks",
43+
new LazySyncProcessExecution(connection, cli, syncType)
44+
);
45+
46+
this.isBackground = true;
47+
this.detail = "$(rocket) Databricks sync";
48+
this.problemMatchers = ["$bricks-sync"];
49+
this.presentationOptions.echo = true;
50+
this.group = TaskGroup.Build;
51+
this.presentationOptions.reveal = TaskRevealKind.Silent;
52+
}
53+
}
54+
55+
/**
56+
* Wrapper around the ProcessExecution class that lazily evaluates the process
57+
* and args properties. This is necessary because the process and args properties
58+
* re not known up front can only be computed dynamically at runtime.
59+
*/
60+
class LazySyncProcessExecution extends ProcessExecution {
61+
private command?: Command;
62+
constructor(
63+
private connection: ConnectionManager,
64+
private cli: CliWrapper,
65+
private syncType: "full" | "incremental"
66+
) {
67+
super("", []);
68+
69+
// hacky way to override properties with getters
70+
Object.defineProperties(this, {
71+
process: {
72+
get: () => {
73+
return this.getSyncCommand().command;
74+
},
75+
},
76+
args: {
77+
get: () => {
78+
return this.getSyncCommand().args;
79+
},
80+
},
81+
options: {
82+
get(): ProcessExecutionOptions {
83+
const workspacePath = workspace.rootPath;
84+
if (!workspacePath) {
85+
window.showErrorMessage(
86+
"Can't start sync: No workspace opened!"
87+
);
88+
throw new Error("!!!!!");
89+
}
90+
91+
return {
92+
cwd: workspacePath,
93+
};
94+
},
95+
},
96+
});
97+
}
98+
99+
getSyncCommand(): Command {
100+
if (this.command) {
101+
return this.command;
102+
}
103+
104+
const me = this.connection.me;
105+
const syncDestination = this.connection.syncDestination;
106+
const profile = this.connection.profile;
107+
108+
if (!me || !profile) {
109+
window.showErrorMessage(
110+
"Can't start sync: Databricks connection not configured!"
111+
);
112+
throw new Error(
113+
"Can't start sync: Databricks connection not configured!"
114+
);
115+
}
116+
if (!syncDestination) {
117+
window.showErrorMessage(
118+
"Can't start sync: Databricks synchronization destination not configured!"
119+
);
120+
throw new Error(
121+
"Can't start sync: Databricks synchronization destination not configured!"
122+
);
123+
}
124+
125+
this.command = this.cli.getSyncCommand(
126+
profile,
127+
me,
128+
syncDestination,
129+
this.syncType
130+
);
131+
132+
return this.command;
133+
}
134+
}

0 commit comments

Comments
 (0)