Skip to content

Commit d8b8ad8

Browse files
authored
Experimental: Create repo from IDE (#418)
- create new repos by specifying https://github.com/fjakobs/empty-repo as the git remote - default branch is called `ide` so it can act as label in the Repos web UI - the empty repo comes with a Readme explaining what's going on https://user-images.githubusercontent.com/40952/216372784-88f60cb9-eb24-4a7a-be52-5d93f6d05c83.mov
1 parent 62e1e98 commit d8b8ad8

File tree

6 files changed

+128
-54
lines changed

6 files changed

+128
-54
lines changed

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,16 @@ import {Loggers} from "../logger";
1919
import {Context, context} from "@databricks/databricks-sdk/dist/context";
2020
import {PackageMetaData} from "../utils/packageJsonUtils";
2121

22-
export const TaskSyncType = {
22+
export const TASK_SYNC_TYPE = {
2323
syncFull: "sync-full",
2424
sync: "sync",
2525
} as const;
26-
type TaskSyncType = (typeof TaskSyncType)[keyof typeof TaskSyncType];
26+
27+
type TaskSyncType = (typeof TASK_SYNC_TYPE)[keyof typeof TASK_SYNC_TYPE];
2728

2829
const cliToTaskSyncType = new Map<SyncType, TaskSyncType>([
29-
["full", TaskSyncType.syncFull],
30-
["incremental", TaskSyncType.sync],
30+
["full", TASK_SYNC_TYPE.syncFull],
31+
["incremental", TASK_SYNC_TYPE.sync],
3132
]);
3233

3334
export class SyncTask extends Task {
@@ -68,7 +69,7 @@ export class SyncTask extends Task {
6869
static killAll() {
6970
window.terminals.forEach((terminal) => {
7071
if (
71-
Object.values(TaskSyncType)
72+
Object.values(TASK_SYNC_TYPE)
7273
.map((e) => e as string)
7374
.includes(terminal.name)
7475
) {

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

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {ConnectionManager} from "./ConnectionManager";
1616
import {UrlUtils} from "../utils";
1717
import {workspaceConfigs} from "../WorkspaceConfigs";
1818
import {WorkspaceFsCommands} from "../workspace-fs";
19+
import {REPO_NAME_SUFFIX} from "./SyncDestination";
1920

2021
function formatQuickPickClusterSize(sizeInMB: number): string {
2122
if (sizeInMB > 1024) {
@@ -230,28 +231,66 @@ export class ConnectionCommands implements Disposable {
230231
label: "Create New Sync Destination",
231232
alwaysShow: true,
232233
detail: workspaceConfigs.enableFilesInWorkspace
233-
? ""
234-
: `Open Databricks in the browser and create a new Repo under /Repo/${me}`,
234+
? `Create a new folder under /Workspace/${me}/.ide as sync destination`
235+
: `Create a new Repo under /Repo/${me} as sync destination`,
235236
},
236237
{
237-
label: "",
238+
label: "Sync Destinations",
238239
kind: QuickPickItemKind.Separator,
239240
},
240241
];
241242

242243
const input = window.createQuickPick();
243244
input.busy = true;
244-
input.items = children;
245245
input.show();
246-
children.push(
247-
...((await rootDir?.children) ?? []).map((entity) => {
248-
return {
249-
label: entity.basename,
250-
detail: entity.path,
251-
path: entity.path,
252-
};
253-
})
254-
);
246+
input.items = children;
247+
if (workspaceConfigs.enableFilesInWorkspace) {
248+
children.push(
249+
...((await rootDir?.children) ?? []).map((entity) => {
250+
return {
251+
label: entity.basename,
252+
detail: entity.path,
253+
path: entity.path,
254+
};
255+
})
256+
);
257+
} else {
258+
const repos = (await rootDir?.children) ?? [];
259+
260+
children.push(
261+
...repos
262+
.filter((entity) => {
263+
return entity.basename.endsWith(REPO_NAME_SUFFIX);
264+
})
265+
.map((entity) => {
266+
return {
267+
label: entity.basename.slice(
268+
0,
269+
-REPO_NAME_SUFFIX.length
270+
),
271+
detail: entity.path,
272+
path: entity.path,
273+
};
274+
})
275+
);
276+
children.push(
277+
{
278+
label: "Repos",
279+
kind: QuickPickItemKind.Separator,
280+
},
281+
...repos
282+
.filter((entity) => {
283+
return !entity.basename.endsWith(REPO_NAME_SUFFIX);
284+
})
285+
.map((entity) => {
286+
return {
287+
label: entity.basename,
288+
detail: entity.path,
289+
path: entity.path,
290+
};
291+
})
292+
);
293+
}
255294
input.items = children;
256295
input.busy = false;
257296

@@ -267,19 +306,11 @@ export class ConnectionCommands implements Disposable {
267306
);
268307
return;
269308
}
270-
if (!workspaceConfigs.enableFilesInWorkspace) {
271-
UrlUtils.openExternal(
272-
(await rootDir?.url) ??
273-
(
274-
await this.connectionManager
275-
.workspaceClient?.apiClient?.host
276-
)?.href ??
277-
""
278-
);
279-
return;
280-
}
281309
const created = await this.wsfsCommands.createFolder(
282-
rootDir
310+
rootDir,
311+
workspaceConfigs.enableFilesInWorkspace
312+
? "Folder"
313+
: "Repo"
283314
);
284315
if (created === undefined) {
285316
return;

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {Uri} from "vscode";
99
import {ConnectionManager} from "./ConnectionManager";
1010

1111
type SyncDestinationType = "workspace" | "repo";
12+
export const REPO_NAME_SUFFIX = ".ide";
1213

1314
/**
1415
* Either Databricks repo or workspace that acts as a sync target for the current workspace.
@@ -120,7 +121,12 @@ export class SyncDestination {
120121
}
121122

122123
get name(): string {
123-
return path.basename(this.wsfsDirPath.path);
124+
const base = path.basename(this.wsfsDirPath.path);
125+
if (base.endsWith(REPO_NAME_SUFFIX)) {
126+
return base.slice(0, -REPO_NAME_SUFFIX.length);
127+
} else {
128+
return base;
129+
}
124130
}
125131

126132
get vscodeWorkspacePathName(): string {

packages/databricks-vscode/src/sync/CodeSynchronizer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {Disposable, Event, EventEmitter, TaskExecution, tasks} from "vscode";
2-
import {SyncTask, TaskSyncType} from "../cli/BricksTasks";
2+
import {SyncTask, TASK_SYNC_TYPE} from "../cli/BricksTasks";
33
import {CliWrapper} from "../cli/CliWrapper";
44
import {ConnectionManager} from "../configuration/ConnectionManager";
55
import {PackageMetaData} from "../utils/packageJsonUtils";
@@ -40,7 +40,7 @@ export class CodeSynchronizer implements Disposable {
4040
const {type, task} = e.execution.task.definition;
4141
if (
4242
type === "databricks" &&
43-
Object.values(TaskSyncType).includes(task)
43+
Object.values(TASK_SYNC_TYPE).includes(task)
4444
) {
4545
this.currentTaskExecution = e.execution;
4646
this._onDidChangeStateEmitter.fire(this.state);
@@ -50,7 +50,7 @@ export class CodeSynchronizer implements Disposable {
5050
const {type, task} = e.execution.task.definition;
5151
if (
5252
type === "databricks" &&
53-
Object.values(TaskSyncType).includes(task)
53+
Object.values(TASK_SYNC_TYPE).includes(task)
5454
) {
5555
this.currentTaskExecution = undefined;
5656
this._onDidChangeStateEmitter.fire(this.state);

packages/databricks-vscode/src/workspace-fs/WorkspaceFsCommands.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {context, Context} from "@databricks/databricks-sdk/dist/context";
33
import {withLogContext} from "@databricks/databricks-sdk/dist/logging";
44
import {Disposable, Uri, window} from "vscode";
55
import {ConnectionManager} from "../configuration/ConnectionManager";
6+
import {REPO_NAME_SUFFIX} from "../configuration/SyncDestination";
67
import {Loggers} from "../logger";
78
import {createDirWizard} from "./createDirectoryWizard";
89
import {WorkspaceFsDataProvider} from "./WorkspaceFsDataProvider";
@@ -21,14 +22,21 @@ export class WorkspaceFsCommands implements Disposable {
2122
Uri.from({scheme: "wsfs", path: element.path})
2223
);
2324
}
25+
2426
@withLogContext(Loggers.Extension)
25-
async createFolder(element?: WorkspaceFsEntity, @context ctx?: Context) {
27+
async createFolder(
28+
element?: WorkspaceFsEntity,
29+
type: "Repo" | "Folder" = "Folder",
30+
@context ctx?: Context
31+
) {
2632
const rootPath =
2733
element?.path ??
2834
this._connectionManager.databricksWorkspace?.currentFsRoot.path;
2935

3036
if (!this._connectionManager.workspaceClient) {
31-
window.showErrorMessage(`Login to create a new directory`);
37+
window.showErrorMessage(
38+
`Please login first to create a new directory`
39+
);
3240
return;
3341
}
3442

@@ -55,16 +63,51 @@ export class WorkspaceFsCommands implements Disposable {
5563
return;
5664
}
5765

58-
const created = await createDirWizard(root, this._workspaceFolder);
66+
const inputPath = await createDirWizard(
67+
this._workspaceFolder,
68+
type === "Repo" ? "Repo Name" : "Directory Name"
69+
);
70+
let created: WorkspaceFsEntity | undefined;
71+
72+
if (inputPath !== undefined) {
73+
if (type === "Repo") {
74+
created = await this.createRepo(
75+
rootPath + "/" + inputPath + REPO_NAME_SUFFIX
76+
);
77+
} else {
78+
created = await root.mkdir(inputPath);
79+
}
80+
if (created === undefined) {
81+
window.showErrorMessage(`Can't create directory ${inputPath}`);
82+
}
83+
}
84+
5985
if (created) {
6086
this._workspaceFsDataProvider.refresh();
6187
return created;
6288
}
6389
}
6490

91+
private async createRepo(repoPath: string) {
92+
const wsClient = this._connectionManager.workspaceClient;
93+
if (!wsClient) {
94+
window.showErrorMessage(`Please login first to create a new repo`);
95+
return;
96+
}
97+
98+
await wsClient.repos.create({
99+
path: repoPath,
100+
provider: "github",
101+
url: "https://github.com/databricks/databricks-empty-ide-project",
102+
});
103+
104+
return await WorkspaceFsEntity.fromPath(wsClient, repoPath);
105+
}
106+
65107
async refresh() {
66108
this._workspaceFsDataProvider.refresh();
67109
}
110+
68111
dispose() {
69112
this.disposables.forEach((i) => i.dispose());
70113
}
Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,21 @@
11
import {Uri, window} from "vscode";
2-
import {WorkspaceFsDir} from "@databricks/databricks-sdk";
32
import path from "path";
43

54
export async function createDirWizard(
6-
root: WorkspaceFsDir,
7-
workspaceFolder: Uri
8-
) {
9-
const inputPath = await window.showInputBox({
10-
title: `Create new directory`,
5+
workspaceFolder: Uri,
6+
title: string
7+
): Promise<string | undefined> {
8+
return await window.showInputBox({
9+
title: title,
1110
placeHolder: path.basename(workspaceFolder.fsPath),
1211
value: path.basename(workspaceFolder.fsPath),
1312
validateInput: (input) => {
14-
const childPath = root.getAbsoluteChildPath(input);
15-
if (childPath === undefined || childPath === root.path) {
16-
return `The path must be a child of ${root.path}`;
13+
if (input === "") {
14+
return "Please enter a name";
15+
}
16+
if (input.includes("/")) {
17+
return "Invalid name: Folders cannot contain '/'";
1718
}
1819
},
1920
});
20-
21-
if (inputPath !== undefined) {
22-
const created = await root.mkdir(inputPath);
23-
if (created === undefined) {
24-
window.showErrorMessage(`Can't create directory ${inputPath}`);
25-
}
26-
return created;
27-
}
2821
}

0 commit comments

Comments
 (0)