Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions packages/databricks-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@
"enablement": "databricks.context.activated && databricks.context.loggedIn && !databricks.context.remoteMode",
"category": "Databricks"
},
{
"command": "databricks.wsfs.createFolder.toolbar",
"title": "Create Folder",
"icon": "$(new-folder)",
"enablement": "databricks.context.activated && databricks.context.loggedIn && !databricks.context.remoteMode",
"category": "Databricks"
},
{
"command": "databricks.wsfs.openInBrowser",
"title": "Open in Browser",
Expand All @@ -213,6 +220,12 @@
"icon": "$(cloud-upload)",
"category": "Databricks"
},
{
"command": "databricks.wsfs.uploadFile.toolbar",
"title": "Upload File",
"icon": "$(cloud-upload)",
"category": "Databricks"
},
{
"command": "databricks.wsfs.downloadFile",
"title": "Download",
Expand Down Expand Up @@ -709,7 +722,7 @@
"group": "navigation@1"
},
{
"command": "databricks.wsfs.createFolder",
"command": "databricks.wsfs.createFolder.toolbar",
"when": "view == workspaceFsView",
"group": "navigation@1"
},
Expand All @@ -724,7 +737,7 @@
"group": "navigation@1"
},
{
"command": "databricks.wsfs.uploadFile",
"command": "databricks.wsfs.uploadFile.toolbar",
"when": "view == workspaceFsView",
"group": "navigation@1"
},
Expand Down Expand Up @@ -1113,6 +1126,10 @@
"command": "databricks.wsfs.createFolder",
"when": "!databricks.context.remoteMode"
},
{
"command": "databricks.wsfs.createFolder.toolbar",
"when": "false"
},
{
"command": "databricks.wsfs.refresh",
"when": "!databricks.context.remoteMode"
Expand All @@ -1137,6 +1154,10 @@
"command": "databricks.wsfs.uploadFile",
"when": "false"
},
{
"command": "databricks.wsfs.uploadFile.toolbar",
"when": "false"
},
{
"command": "databricks.utils.openExternal",
"when": "false"
Expand Down
21 changes: 16 additions & 5 deletions packages/databricks-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,11 +393,15 @@ export async function activate(
connectionManager
);
const workspaceFsFsp = new WorkspaceFsFileSystemProvider(connectionManager);
const workspaceFsTreeView = window.createTreeView("workspaceFsView", {
treeDataProvider: workspaceFsDataProvider,
});
const workspaceFsCommands = new WorkspaceFsCommands(
workspaceFolderManager,
connectionManager,
workspaceFsDataProvider,
workspaceFsFsp
workspaceFsFsp,
workspaceFsTreeView
);

context.subscriptions.push(
Expand All @@ -409,10 +413,7 @@ export async function activate(
}
),
workspaceFsFsp,
window.registerTreeDataProvider(
"workspaceFsView",
workspaceFsDataProvider
),
workspaceFsTreeView,
telemetry.registerCommand(
"databricks.wsfs.refresh",
workspaceFsCommands.refresh,
Expand All @@ -423,6 +424,11 @@ export async function activate(
workspaceFsCommands.createFolder,
workspaceFsCommands
),
telemetry.registerCommand(
"databricks.wsfs.createFolder.toolbar",
workspaceFsCommands.createFolderFromToolbar,
workspaceFsCommands
),
telemetry.registerCommand(
"databricks.wsfs.openInBrowser",
workspaceFsCommands.openInBrowser,
Expand All @@ -443,6 +449,11 @@ export async function activate(
workspaceFsCommands.uploadFile,
workspaceFsCommands
),
telemetry.registerCommand(
"databricks.wsfs.uploadFile.toolbar",
workspaceFsCommands.uploadFileFromToolbar,
workspaceFsCommands
),
telemetry.registerCommand(
"databricks.wsfs.downloadFile",
workspaceFsCommands.downloadFile,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import assert from "assert";
import {EventEmitter, TreeView} from "vscode";
import {mock, instance, when} from "ts-mockito";
import {WorkspaceFsCommands} from "./WorkspaceFsCommands";
import {WorkspaceFsEntity} from "../sdk-extensions";
import {ConnectionManager} from "../configuration/ConnectionManager";
import {WorkspaceFsDataProvider} from "./WorkspaceFsDataProvider";
import {WorkspaceFsFileSystemProvider} from "./WorkspaceFsFileSystemProvider";
import {WorkspaceFolderManager} from "../vscode-objs/WorkspaceFolderManager";
import {DatabricksWorkspace} from "../configuration/DatabricksWorkspace";
import {RemoteUri} from "../sync/SyncDestination";

// Minimal fake TreeView that tracks selection and fires onDidChangeSelection
class FakeTreeView {
selection: ReadonlyArray<WorkspaceFsEntity> = [];
private _emitter = new EventEmitter<{
selection: WorkspaceFsEntity[];
}>();
readonly onDidChangeSelection = this._emitter.event;

simulateSelect(element: WorkspaceFsEntity | undefined): void {
this.selection = element ? [element] : [];
this._emitter.fire({selection: [...this.selection]});
}
}

function makeEntity(path: string): WorkspaceFsEntity {
return {path, type: "DIRECTORY"} as unknown as WorkspaceFsEntity;
}

describe("WorkspaceFsCommands – target folder resolution", () => {
const ROOT_PATH = "/Users/me";

let fakeTreeView: FakeTreeView;
let mockConnectionManager: ConnectionManager;
let commands: WorkspaceFsCommands;
let capturedRootPath: string | undefined;

const entityA = makeEntity("/Users/me/A");
const entityB = makeEntity("/Users/me/B");
const fileEntity = {
path: "/Users/me/A/note.py",
type: "FILE",
} as unknown as WorkspaceFsEntity;

beforeEach(() => {
fakeTreeView = new FakeTreeView();

const mockDatabricksWorkspace = mock<DatabricksWorkspace>();
when(mockDatabricksWorkspace.currentFsRoot).thenReturn(
new RemoteUri(ROOT_PATH)
);

mockConnectionManager = mock<ConnectionManager>();
when(mockConnectionManager.workspaceClient).thenReturn(
undefined as any
);
when(mockConnectionManager.databricksWorkspace).thenReturn(
instance(mockDatabricksWorkspace)
);

commands = new WorkspaceFsCommands(
instance(mock<WorkspaceFolderManager>()),
instance(mockConnectionManager),
instance(mock<WorkspaceFsDataProvider>()),
instance(mock<WorkspaceFsFileSystemProvider>()),
fakeTreeView as unknown as TreeView<WorkspaceFsEntity>
);

// Replace getValidRoot to capture rootPath and short-circuit execution
capturedRootPath = undefined;
(commands as any).getValidRoot = async (rootPath?: string) => {
capturedRootPath = rootPath;
return undefined;
};
});

// Context-menu command: element is the right-clicked node; treeView
// selection is irrelevant.
describe("createFolder (context menu)", () => {
it("no element → targets root", async () => {
await commands.createFolder(undefined);
assert.strictEqual(capturedRootPath, ROOT_PATH);
});

it("element=A → targets A", async () => {
await commands.createFolder(entityA);
assert.strictEqual(capturedRootPath, entityA.path);
});

it("element=B while A is selected → targets B", async () => {
fakeTreeView.simulateSelect(entityA);
await commands.createFolder(entityB);
assert.strictEqual(capturedRootPath, entityB.path);
});
});

// Toolbar command: VS Code passes the currently-selected node as element.
// resolveTargetElementForToolbar uses treeView.selection to detect whether
// something is truly selected; if not, it falls back to root.
describe("createFolderFromToolbar (toolbar)", () => {
it("nothing selected → targets root", async () => {
await commands.createFolderFromToolbar(undefined);
assert.strictEqual(capturedRootPath, ROOT_PATH);
});

it("A selected, toolbar clicked → targets A", async () => {
fakeTreeView.simulateSelect(entityA);
// VS Code passes the selected node as element on toolbar click
await commands.createFolderFromToolbar(entityA);
assert.strictEqual(capturedRootPath, entityA.path);
});

it("selection cleared before toolbar click → targets root", async () => {
fakeTreeView.simulateSelect(entityA);
fakeTreeView.simulateSelect(undefined);
await commands.createFolderFromToolbar(undefined);
assert.strictEqual(capturedRootPath, ROOT_PATH);
});

it("element passed but nothing selected (edge case) → targets root", async () => {
// treeView.selection is empty; resolveTargetElementForToolbar
// ignores the element and returns undefined → root
await commands.createFolderFromToolbar(entityA);
assert.strictEqual(capturedRootPath, ROOT_PATH);
});
});

describe("uploadFile (context menu)", () => {
// doUploadFile checks workspaceClient before reaching getValidRoot;
// provide a non-null client so root-path resolution is exercised.
beforeEach(() => {
when(mockConnectionManager.workspaceClient).thenReturn({} as any);
});

it("no element → targets root", async () => {
await commands.uploadFile(undefined);
assert.strictEqual(capturedRootPath, ROOT_PATH);
});

it("element=A (directory) → targets A", async () => {
await commands.uploadFile(entityA);
assert.strictEqual(capturedRootPath, entityA.path);
});

it("element=B while A is selected → targets B", async () => {
fakeTreeView.simulateSelect(entityA);
await commands.uploadFile(entityB);
assert.strictEqual(capturedRootPath, entityB.path);
});

it("element=file (non-directory) → targets root", async () => {
await commands.uploadFile(fileEntity);
assert.strictEqual(capturedRootPath, ROOT_PATH);
});
});

describe("uploadFileFromToolbar (toolbar)", () => {
beforeEach(() => {
when(mockConnectionManager.workspaceClient).thenReturn({} as any);
});

it("nothing selected → targets root", async () => {
await commands.uploadFileFromToolbar(undefined);
assert.strictEqual(capturedRootPath, ROOT_PATH);
});

it("A selected, toolbar clicked → targets A", async () => {
fakeTreeView.simulateSelect(entityA);
await commands.uploadFileFromToolbar(entityA);
assert.strictEqual(capturedRootPath, entityA.path);
});

it("selection cleared before toolbar click → targets root", async () => {
fakeTreeView.simulateSelect(entityA);
fakeTreeView.simulateSelect(undefined);
await commands.uploadFileFromToolbar(undefined);
assert.strictEqual(capturedRootPath, ROOT_PATH);
});

it("element passed but nothing selected (edge case) → targets root", async () => {
await commands.uploadFileFromToolbar(entityA);
assert.strictEqual(capturedRootPath, ROOT_PATH);
});

it("file selected, toolbar clicked → targets root", async () => {
fakeTreeView.simulateSelect(fileEntity);
await commands.uploadFileFromToolbar(fileEntity);
assert.strictEqual(capturedRootPath, ROOT_PATH);
});
});
});
Loading
Loading