|
| 1 | +import { |
| 2 | + QuickPickItem, |
| 3 | + QuickPickItemKind, |
| 4 | + Uri, |
| 5 | + window, |
| 6 | + TerminalLocation, |
| 7 | + commands, |
| 8 | +} from "vscode"; |
| 9 | +import {logging} from "@databricks/databricks-sdk"; |
| 10 | +import {Loggers} from "../logger"; |
| 11 | +import {AuthProvider} from "../configuration/auth/AuthProvider"; |
| 12 | +import {LoginWizard} from "../configuration/LoginWizard"; |
| 13 | +import {CliWrapper} from "../cli/CliWrapper"; |
| 14 | +import {ConfigModel} from "../configuration/models/ConfigModel"; |
| 15 | +import {getSubProjects} from "./BundleFileSet"; |
| 16 | + |
| 17 | +export async function promptToOpenSubProjects( |
| 18 | + projects: {absolute: Uri; relative: Uri}[] |
| 19 | +) { |
| 20 | + type OpenProjectItem = QuickPickItem & {uri?: Uri}; |
| 21 | + const items: OpenProjectItem[] = projects.map((project) => { |
| 22 | + return { |
| 23 | + uri: project.absolute, |
| 24 | + label: project.relative.fsPath, |
| 25 | + detail: project.absolute.fsPath, |
| 26 | + }; |
| 27 | + }); |
| 28 | + items.push( |
| 29 | + {label: "", kind: QuickPickItemKind.Separator}, |
| 30 | + {label: "Choose another folder"} |
| 31 | + ); |
| 32 | + const options = { |
| 33 | + title: "Select the project you want to open", |
| 34 | + }; |
| 35 | + const item = await window.showQuickPick<OpenProjectItem>(items, options); |
| 36 | + if (!item) { |
| 37 | + return; |
| 38 | + } |
| 39 | + await commands.executeCommand("vscode.openFolder", item.uri); |
| 40 | +} |
| 41 | + |
| 42 | +export class BundleInitWizard { |
| 43 | + private logger = logging.NamedLogger.getOrCreate(Loggers.Extension); |
| 44 | + |
| 45 | + constructor(private cli: CliWrapper) {} |
| 46 | + |
| 47 | + public async initNewProject( |
| 48 | + workspaceUri?: Uri, |
| 49 | + existingAuthProvider?: AuthProvider, |
| 50 | + configModel?: ConfigModel |
| 51 | + ) { |
| 52 | + const authProvider = await this.configureAuthForBundleInit( |
| 53 | + existingAuthProvider, |
| 54 | + configModel |
| 55 | + ); |
| 56 | + if (!authProvider) { |
| 57 | + this.logger.debug( |
| 58 | + "No valid auth providers, can't proceed with bundle init wizard" |
| 59 | + ); |
| 60 | + return; |
| 61 | + } |
| 62 | + const parentFolder = await this.promptForParentFolder(workspaceUri); |
| 63 | + if (!parentFolder) { |
| 64 | + this.logger.debug("No parent folder provided"); |
| 65 | + return; |
| 66 | + } |
| 67 | + await this.bundleInitInTerminal(parentFolder, authProvider); |
| 68 | + this.logger.debug( |
| 69 | + "Finished bundle init wizard, detecting projects to initialize or open" |
| 70 | + ); |
| 71 | + const projects = await getSubProjects(parentFolder); |
| 72 | + if (projects.length > 0) { |
| 73 | + this.logger.debug( |
| 74 | + `Detected ${projects.length} sub projects after the init wizard, prompting to open one` |
| 75 | + ); |
| 76 | + await promptToOpenSubProjects(projects); |
| 77 | + } else { |
| 78 | + this.logger.debug( |
| 79 | + `No projects detected after the init wizard, showing notification to open a folder manually` |
| 80 | + ); |
| 81 | + const choice = await window.showInformationMessage( |
| 82 | + `We haven't detected any Databricks projects in "${parentFolder.fsPath}". If you initialized your project somewhere else, please open the folder manually.`, |
| 83 | + "Open Folder" |
| 84 | + ); |
| 85 | + if (choice === "Open Folder") { |
| 86 | + await commands.executeCommand("vscode.openFolder"); |
| 87 | + } |
| 88 | + } |
| 89 | + return parentFolder; |
| 90 | + } |
| 91 | + |
| 92 | + private async configureAuthForBundleInit( |
| 93 | + authProvider?: AuthProvider, |
| 94 | + configModel?: ConfigModel |
| 95 | + ): Promise<AuthProvider | undefined> { |
| 96 | + if (authProvider) { |
| 97 | + const response = await this.promptToUseExistingAuth(authProvider); |
| 98 | + if (response.cancelled) { |
| 99 | + return undefined; |
| 100 | + } else if (!response.approved) { |
| 101 | + authProvider = undefined; |
| 102 | + } |
| 103 | + } |
| 104 | + if (!authProvider) { |
| 105 | + authProvider = await LoginWizard.run(this.cli, configModel); |
| 106 | + } |
| 107 | + if (authProvider && (await authProvider.check())) { |
| 108 | + return authProvider; |
| 109 | + } else { |
| 110 | + return undefined; |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + private async promptToUseExistingAuth(authProvider: AuthProvider) { |
| 115 | + type AuthSelectionItem = QuickPickItem & {approved: boolean}; |
| 116 | + const items: AuthSelectionItem[] = [ |
| 117 | + { |
| 118 | + label: "Use current auth", |
| 119 | + detail: `Host: ${authProvider.host.hostname}`, |
| 120 | + approved: true, |
| 121 | + }, |
| 122 | + { |
| 123 | + label: "Setup new auth", |
| 124 | + approved: false, |
| 125 | + }, |
| 126 | + ]; |
| 127 | + const options = { |
| 128 | + title: "What auth do you want to use for the new project?", |
| 129 | + }; |
| 130 | + const item = await window.showQuickPick<AuthSelectionItem>( |
| 131 | + items, |
| 132 | + options |
| 133 | + ); |
| 134 | + return { |
| 135 | + cancelled: item === undefined, |
| 136 | + approved: item?.approved ?? false, |
| 137 | + }; |
| 138 | + } |
| 139 | + |
| 140 | + private async bundleInitInTerminal( |
| 141 | + parentFolder: Uri, |
| 142 | + authProvider: AuthProvider |
| 143 | + ) { |
| 144 | + const terminal = window.createTerminal({ |
| 145 | + name: "Databricks Project Init", |
| 146 | + isTransient: true, |
| 147 | + location: TerminalLocation.Editor, |
| 148 | + env: this.cli.getBundleInitEnvVars(authProvider), |
| 149 | + }); |
| 150 | + const args = [ |
| 151 | + "bundle", |
| 152 | + "init", |
| 153 | + "--output-dir", |
| 154 | + this.cli.escapePathArgument(parentFolder.fsPath), |
| 155 | + ].join(" "); |
| 156 | + const initialPrompt = `clear; echo "Executing: databricks ${args}\nFollow the steps below to create your new Databricks project.\n"`; |
| 157 | + const finalPrompt = `echo "Press any key to close the terminal and continue ..."; read; exit`; |
| 158 | + terminal.sendText( |
| 159 | + `${initialPrompt}; ${this.cli.cliPath} ${args}; ${finalPrompt}` |
| 160 | + ); |
| 161 | + return new Promise<void>((resolve) => { |
| 162 | + const closeEvent = window.onDidCloseTerminal(async (t) => { |
| 163 | + if (t !== terminal) { |
| 164 | + return; |
| 165 | + } |
| 166 | + closeEvent.dispose(); |
| 167 | + resolve(); |
| 168 | + }); |
| 169 | + }); |
| 170 | + } |
| 171 | + |
| 172 | + private async promptForParentFolder( |
| 173 | + workspaceUri?: Uri |
| 174 | + ): Promise<Uri | undefined> { |
| 175 | + const quickPick = window.createQuickPick(); |
| 176 | + const openFolderLabel = "Open folder selection dialog"; |
| 177 | + const initialValue = workspaceUri?.fsPath || process.env.HOME; |
| 178 | + if (initialValue) { |
| 179 | + quickPick.value = initialValue; |
| 180 | + } |
| 181 | + quickPick.title = |
| 182 | + "Provide a path to a folder where you would want your new project to be"; |
| 183 | + quickPick.items = createParentFolderQuickPickItems( |
| 184 | + quickPick.value, |
| 185 | + openFolderLabel |
| 186 | + ); |
| 187 | + quickPick.show(); |
| 188 | + const disposables = [ |
| 189 | + quickPick.onDidChangeValue(() => { |
| 190 | + quickPick.items = createParentFolderQuickPickItems( |
| 191 | + quickPick.value, |
| 192 | + openFolderLabel |
| 193 | + ); |
| 194 | + }), |
| 195 | + ]; |
| 196 | + const choice = await new Promise<QuickPickItem | undefined>( |
| 197 | + (resolve) => { |
| 198 | + disposables.push( |
| 199 | + quickPick.onDidAccept(() => |
| 200 | + resolve(quickPick.selectedItems[0]) |
| 201 | + ), |
| 202 | + quickPick.onDidHide(() => resolve(undefined)) |
| 203 | + ); |
| 204 | + } |
| 205 | + ); |
| 206 | + disposables.forEach((d) => d.dispose()); |
| 207 | + quickPick.hide(); |
| 208 | + if (!choice) { |
| 209 | + return; |
| 210 | + } |
| 211 | + if (choice.label !== openFolderLabel) { |
| 212 | + return Uri.file(choice.label); |
| 213 | + } |
| 214 | + const choices = await window.showOpenDialog({ |
| 215 | + title: "Chose a folder where you would want your new project to be", |
| 216 | + openLabel: "Select folder", |
| 217 | + defaultUri: workspaceUri, |
| 218 | + canSelectFolders: true, |
| 219 | + canSelectFiles: false, |
| 220 | + canSelectMany: false, |
| 221 | + }); |
| 222 | + return choices ? choices[0] : undefined; |
| 223 | + } |
| 224 | +} |
| 225 | + |
| 226 | +function createParentFolderQuickPickItems( |
| 227 | + value: string | undefined, |
| 228 | + openFolderLabel: string |
| 229 | +) { |
| 230 | + const items: QuickPickItem[] = value |
| 231 | + ? [{label: value, alwaysShow: true}] |
| 232 | + : []; |
| 233 | + items.push( |
| 234 | + {label: "", kind: QuickPickItemKind.Separator, alwaysShow: true}, |
| 235 | + {label: openFolderLabel, alwaysShow: true} |
| 236 | + ); |
| 237 | + return items; |
| 238 | +} |
0 commit comments