Skip to content

Commit 128962b

Browse files
Files in Workspace support (#337)
Patched bricks version for testing databricks/cli#169 Co-authored-by: Fabian Jakobs <fabian.jakobs@databricks.com>
1 parent c101af2 commit 128962b

27 files changed

+730
-175
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import assert from "assert";
2+
import {posix} from "path";
3+
import {anything, deepEqual, instance, mock, when} from "ts-mockito";
4+
import {WorkspaceService} from "../../apis/workspace";
5+
import {WorkspaceClient} from "../../WorkspaceClient";
6+
import {isDirectory} from "./utils";
7+
import {WorkspaceFsEntity} from "./WorkspaceFsEntity";
8+
9+
describe(__filename, () => {
10+
let mockWorkspaceClient: WorkspaceClient;
11+
let mockWorkspaceService: WorkspaceService;
12+
13+
before(() => {
14+
mockWorkspaceClient = mock(WorkspaceClient);
15+
mockWorkspaceService = mock(WorkspaceService);
16+
when(mockWorkspaceClient.workspace).thenReturn(
17+
instance(mockWorkspaceService)
18+
);
19+
});
20+
21+
function mockDirectory(path: string) {
22+
when(
23+
mockWorkspaceService.getStatus(deepEqual({path}), anything())
24+
).thenResolve({
25+
// eslint-disable-next-line @typescript-eslint/naming-convention
26+
object_type: "DIRECTORY",
27+
// eslint-disable-next-line @typescript-eslint/naming-convention
28+
object_id: 123,
29+
path: path,
30+
});
31+
}
32+
33+
it("should return correct absolute child path", async () => {
34+
const path = "/root/a/b";
35+
mockDirectory(path);
36+
37+
const root = await WorkspaceFsEntity.fromPath(
38+
instance(mockWorkspaceClient),
39+
path
40+
);
41+
assert.ok(isDirectory(root));
42+
43+
assert.equal(root.getAbsoluteChildPath(path), path);
44+
assert.equal(
45+
root.getAbsoluteChildPath(posix.resolve(path, "..", "..")),
46+
undefined
47+
);
48+
assert.equal(
49+
root.getAbsoluteChildPath(posix.resolve(path, "..")),
50+
undefined
51+
);
52+
assert.ok(
53+
root.getAbsoluteChildPath(posix.resolve(path, "c", "..", "..")) ===
54+
undefined
55+
);
56+
assert.ok(
57+
root.getAbsoluteChildPath(posix.resolve(path, "c", "d")) ===
58+
posix.resolve(path, "c", "d")
59+
);
60+
});
61+
});

packages/databricks-sdk-js/src/services/wsfs/WorkspaceFsDir.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export class WorkspaceFsDir extends WorkspaceFsEntity {
1414

1515
if (
1616
!posix.isAbsolute(relative) &&
17-
!relative.startsWith(".." + posix.sep)
17+
!relative.startsWith(".." + posix.sep) &&
18+
relative !== ".."
1819
) {
1920
return resolved;
2021
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
// Place your Extension workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
3+
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
4+
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
5+
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
6+
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
7+
// Placeholders with the same ids are connected.
8+
// Example:
9+
// "Print to console": {
10+
// "scope": "javascript,typescript",
11+
// "prefix": "log",
12+
// "body": [
13+
// "console.log('$1');",
14+
// "$2"
15+
// ],
16+
// "description": "Log output to console"
17+
// }
18+
"Disposable Class": {
19+
"scope": "typescript",
20+
"prefix": "disposeCls",
21+
"body": [
22+
"import {Disposable} from \"vscode\";",
23+
"",
24+
"export class $1 implements Disposable {",
25+
"\tprivate disposables: Disposable[] = [];",
26+
"",
27+
"\tconstructor($2){}",
28+
"",
29+
"\tdispose() {",
30+
"\t\tthis.disposables.forEach((i) => i.dispose());",
31+
"\t}",
32+
"}"
33+
]
34+
}
35+
}

packages/databricks-vscode/package.json

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,21 @@
171171
"command": "databricks.utils.openExternal",
172172
"title": "Open link externally",
173173
"icon": "$(link)"
174+
},
175+
{
176+
"command": "databricks.wsfs.attachSyncDestination",
177+
"title": "Attach sync destination",
178+
"icon": "$(plug)"
179+
},
180+
{
181+
"command": "databricks.wsfs.refresh",
182+
"title": "Refresh",
183+
"icon": "$(refresh)"
184+
},
185+
{
186+
"command": "databricks.wsfs.createFolder",
187+
"title": "Create Folder",
188+
"icon": "$(new-folder)"
174189
}
175190
],
176191
"viewsContainers": {
@@ -188,6 +203,11 @@
188203
"id": "configurationView",
189204
"name": "Configuration"
190205
},
206+
{
207+
"id": "workspaceFsView",
208+
"name": "Workspace explorer",
209+
"when": "config.databricks.sync.destinationType == workspace"
210+
},
191211
{
192212
"id": "clusterView",
193213
"name": "Clusters"
@@ -226,6 +246,16 @@
226246
"command": "databricks.cluster.refresh",
227247
"when": "view == clusterView",
228248
"group": "navigation@2"
249+
},
250+
{
251+
"command": "databricks.wsfs.refresh",
252+
"when": "view == workspaceFsView",
253+
"group": "navigation@1"
254+
},
255+
{
256+
"command": "databricks.wsfs.createFolder",
257+
"when": "view == workspaceFsView",
258+
"group": "navigation@1"
229259
}
230260
],
231261
"databricks.cluster.filter": [
@@ -287,8 +317,16 @@
287317
},
288318
{
289319
"command": "databricks.utils.openExternal",
290-
"when": "view == configurationView && viewItem =~ /^.*databricks-link.*$/ || view == clusterView && viewItem =~ /^.*databricks-link.*$/",
320+
"when": "viewItem =~ /^.*databricks-link.*$/ ",
291321
"group": "inline@0"
322+
},
323+
{
324+
"command": "databricks.wsfs.attachSyncDestination",
325+
"when": "view == workspaceFsView && viewItem =~ /^wsfs.(directory|repo).*$/ && config.databricks.sync.destinationType == workspace || view == workspaceFsView && viewItem =~ /^wsfs.repo.*$/ && config.databricks.sync.destinationType == repo"
326+
},
327+
{
328+
"command": "databricks.wsfs.createFolder",
329+
"when": "view == workspaceFsView && viewItem =~ /^wsfs.(directory|repo).*$/ && config.databricks.sync.destinationType == workspace"
292330
}
293331
],
294332
"editor/title/run": [
@@ -311,6 +349,10 @@
311349
{
312350
"command": "databricks.run.runEditorContentsAsWorkflow",
313351
"when": "resourceLangId == python"
352+
},
353+
{
354+
"command": "databricks.wsfs.createFolder",
355+
"when": "config.databricks.sync.destinationType == workspace"
314356
}
315357
],
316358
"explorer/context": [
@@ -502,6 +544,19 @@
502544
"type": "boolean",
503545
"default": false,
504546
"description": "Enable verbose logging for bricks CLI (sync mode)."
547+
},
548+
"databricks.sync.destinationType": {
549+
"type": "string",
550+
"default": "repo",
551+
"enum": [
552+
"workspace",
553+
"repo"
554+
],
555+
"description": "Use a folder in Workspace or a Databricks Repo as sync destination (Reload for changes to take effect).",
556+
"enumDescriptions": [
557+
"Use a folder in Workspace as sync destination",
558+
"Use a Repo as sync destination"
559+
]
505560
}
506561
}
507562
}

packages/databricks-vscode/src/WorkspaceConfigs.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,12 @@ export const workspaceConfigs = {
4343
?.get<boolean>("bricks.verboseMode") ?? false
4444
);
4545
},
46+
get enableFilesInWorkspace() {
47+
return (
48+
(workspace
49+
.getConfiguration("databricks")
50+
?.get<"repo" | "workspace">("sync.destinationType") ??
51+
"repo") === "workspace"
52+
);
53+
},
4654
};

packages/databricks-vscode/src/cli/CliWrapper.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {execFile as execFileCb} from "node:child_process";
66

77
import {CliWrapper} from "./CliWrapper";
88
import {instance, mock} from "ts-mockito";
9-
import {Repo} from "@databricks/databricks-sdk";
9+
import {WorkspaceFsRepo} from "@databricks/databricks-sdk";
1010

1111
const execFile = promisify(execFileCb);
1212

@@ -24,7 +24,7 @@ describe(__filename, () => {
2424
},
2525
} as any);
2626
const mapper = new SyncDestination(
27-
instance(mock(Repo)),
27+
instance(mock(WorkspaceFsRepo)),
2828
Uri.from({
2929
scheme: "wsfs",
3030
path: "/Workspace/Repos/fabian.jakobs@databricks.com/notebook-best-practices",

packages/databricks-vscode/src/cli/CliWrapper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class CliWrapper {
3636
const args = [
3737
"sync",
3838
"--remote-path",
39-
syncDestination.relativeRepoPath,
39+
syncDestination.relativeWsfsDirPath,
4040
"--watch",
4141
];
4242
if (syncType === "full") {

packages/databricks-vscode/src/cluster/ClusterListDataProvider.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,7 @@ export class ClusterListDataProvider
6565
return [];
6666
}
6767
} else {
68-
return (() => {
69-
return this.model.roots;
70-
})();
68+
return this.model.roots;
7169
}
7270
}
7371

packages/databricks-vscode/src/configuration/AzureCliCheck.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import {WorkspaceClient} from "@databricks/databricks-sdk";
1+
import {ExecUtils, WorkspaceClient} from "@databricks/databricks-sdk";
22
import {commands, Disposable, Uri, window} from "vscode";
33
import {AuthProvider} from "./auth/AuthProvider";
4-
import {execFileWithShell} from "@databricks/databricks-sdk/dist/config/execUtils";
54

65
export type Step<S, N> = () => Promise<
76
SuccessResult<S> | NextResult<N> | ErrorResult
@@ -181,7 +180,7 @@ export class AzureCliCheck implements Disposable {
181180
// check if Azure CLI is installed
182181
public async hasAzureCli(): Promise<boolean> {
183182
try {
184-
const {stdout} = await execFileWithShell(this.azBinPath, [
183+
const {stdout} = await ExecUtils.execFileWithShell(this.azBinPath, [
185184
"--version",
186185
]);
187186
if (stdout.indexOf("azure-cli") !== -1) {
@@ -214,10 +213,10 @@ export class AzureCliCheck implements Disposable {
214213
// check if Azure CLI is logged in
215214
public async isAzureCliLoggedIn(): Promise<boolean> {
216215
try {
217-
const {stdout, stderr} = await execFileWithShell(this.azBinPath, [
218-
"account",
219-
"list",
220-
]);
216+
const {stdout, stderr} = await ExecUtils.execFileWithShell(
217+
this.azBinPath,
218+
["account", "list"]
219+
);
221220
if (stdout === "[]") {
222221
return false;
223222
}

packages/databricks-vscode/src/configuration/ConfigurationDataProvider.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,10 @@ describe(__filename, () => {
145145
contextValue: "syncDetached",
146146
iconPath: {
147147
color: undefined,
148-
id: "repo",
148+
id: "file-directory",
149149
},
150-
id: "REPO",
151-
label: 'Repo - "None attached"',
150+
id: "SYNC-DESTINATION",
151+
label: 'Sync Destination - "None attached"',
152152
},
153153
]);
154154
});

0 commit comments

Comments
 (0)