diff --git a/extensions/sql-database-projects/package.json b/extensions/sql-database-projects/package.json index e2c70ffc19..e7c757ae2c 100644 --- a/extensions/sql-database-projects/package.json +++ b/extensions/sql-database-projects/package.json @@ -351,6 +351,11 @@ "command": "sqlDatabaseProjects.openContainingFolder", "when": "view == dataworkspace.views.main && viewItem == databaseProject.itemType.project", "group": "9_dbProjectsLast@8" + }, + { + "command": "sqlDatabaseProjects.createProjectFromDatabase", + "when": "!azdataAvailable && view == objectExplorer && viewItem =~ /^(disconnectedServer|Server|Database)$/", + "group": "sqldbproj@1" } ], "objectExplorer/item/context": [ diff --git a/extensions/sql-database-projects/src/controllers/mainController.ts b/extensions/sql-database-projects/src/controllers/mainController.ts index aa5da33f30..1640c7b151 100644 --- a/extensions/sql-database-projects/src/controllers/mainController.ts +++ b/extensions/sql-database-projects/src/controllers/mainController.ts @@ -5,6 +5,7 @@ import type * as azdataType from 'azdata'; import * as vscode from 'vscode'; +import * as vscodeMssql from 'vscode-mssql'; import * as templates from '../templates/templates'; import * as path from 'path'; @@ -50,7 +51,7 @@ export default class MainController implements vscode.Disposable { vscode.commands.registerCommand('sqlDatabaseProjects.build', async (node: WorkspaceTreeItem) => { await this.projectsController.buildProject(node); }); vscode.commands.registerCommand('sqlDatabaseProjects.publish', async (node: WorkspaceTreeItem) => { this.projectsController.publishProject(node); }); vscode.commands.registerCommand('sqlDatabaseProjects.schemaCompare', async (node: WorkspaceTreeItem) => { await this.projectsController.schemaCompare(node); }); - vscode.commands.registerCommand('sqlDatabaseProjects.createProjectFromDatabase', async (profile: azdataType.IConnectionProfile) => { await this.projectsController.createProjectFromDatabase(profile); }); + vscode.commands.registerCommand('sqlDatabaseProjects.createProjectFromDatabase', async (context: azdataType.IConnectionProfile | vscodeMssql.ITreeNodeInfo | undefined) => { await this.projectsController.createProjectFromDatabase(context); }); vscode.commands.registerCommand('sqlDatabaseProjects.newScript', async (node: WorkspaceTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.script); }); vscode.commands.registerCommand('sqlDatabaseProjects.newPreDeploymentScript', async (node: WorkspaceTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.preDeployScript); }); diff --git a/extensions/sql-database-projects/src/controllers/projectController.ts b/extensions/sql-database-projects/src/controllers/projectController.ts index d1b403aa45..170ac70e50 100644 --- a/extensions/sql-database-projects/src/controllers/projectController.ts +++ b/extensions/sql-database-projects/src/controllers/projectController.ts @@ -863,10 +863,10 @@ export class ProjectsController { * Creates a new SQL database project from the existing database, * prompting the user for a name, file path location and extract target */ - public async createProjectFromDatabase(context: azdataType.IConnectionProfile | any): Promise { + public async createProjectFromDatabase(context: azdataType.IConnectionProfile | mssqlVscode.ITreeNodeInfo | undefined): Promise { const profile = this.getConnectionProfileFromContext(context); if (utils.getAzdataApi()) { - let createProjectFromDatabaseDialog = this.getCreateProjectFromDatabaseDialog(profile); + let createProjectFromDatabaseDialog = this.getCreateProjectFromDatabaseDialog(profile as azdataType.IConnectionProfile); createProjectFromDatabaseDialog.createProjectFromDatabaseCallback = async (model) => await this.createProjectFromDatabaseCallback(model); @@ -874,7 +874,15 @@ export class ProjectsController { return createProjectFromDatabaseDialog; } else { - const model = await createNewProjectFromDatabaseWithQuickpick(); + if (context) { + // The profile we get from VS Code is for the overall server connection and isn't updated based on the database node + // the command was launched from like it is in ADS. So get the actual database name from the MSSQL extension and + // update the connection info here. + const treeNodeContext = context as mssqlVscode.ITreeNodeInfo; + const databaseName = (await utils.getVscodeMssqlApi()).getDatabaseNameFromTreeNode(treeNodeContext); + (profile as mssqlVscode.IConnectionInfo).database = databaseName; + } + const model = await createNewProjectFromDatabaseWithQuickpick(profile as mssqlVscode.IConnectionInfo); if (model) { await this.createProjectFromDatabaseCallback(model); } @@ -925,14 +933,14 @@ export class ProjectsController { } } - private getConnectionProfileFromContext(context: azdataType.IConnectionProfile | any): azdataType.IConnectionProfile | undefined { + private getConnectionProfileFromContext(context: azdataType.IConnectionProfile | mssqlVscode.ITreeNodeInfo | undefined): azdataType.IConnectionProfile | mssqlVscode.IConnectionInfo | undefined { if (!context) { return undefined; } // depending on where import new project is launched from, the connection profile could be passed as just // the profile or it could be wrapped in another object - return (context).connectionProfile ? (context).connectionProfile : context; + return (context)?.connectionProfile ?? (context as mssqlVscode.ITreeNodeInfo).connectionInfo ?? context; } public async createProjectFromDatabaseApiCall(model: ImportDataModel): Promise { diff --git a/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseQuickpick.ts b/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseQuickpick.ts index f9a8ee86db..ee88ff7670 100644 --- a/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseQuickpick.ts +++ b/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseQuickpick.ts @@ -14,31 +14,48 @@ import { mapExtractTargetEnum } from './createProjectFromDatabaseDialog'; /** * Create flow for a New Project using only VS Code-native APIs such as QuickPick + * @param connectionInfo Optional connection info to use instead of prompting the user for a connection */ -export async function createNewProjectFromDatabaseWithQuickpick(): Promise { +export async function createNewProjectFromDatabaseWithQuickpick(connectionInfo?: IConnectionInfo): Promise { + + const vscodeMssqlApi = await getVscodeMssqlApi(); // 1. Select connection - const vscodeMssqlApi = await getVscodeMssqlApi(); - let connectionProfile: IConnectionInfo | undefined = undefined; + // Use passed in profile if we have one - otherwise prompt user to select one + let connectionProfile: IConnectionInfo | undefined = connectionInfo ?? await vscodeMssqlApi.promptForConnection(true); + if (!connectionProfile) { + // User cancelled + return undefined; + } let connectionUri: string = ''; let dbs: string[] | undefined = undefined; while (!dbs) { - connectionProfile = await vscodeMssqlApi.promptForConnection(true); - if (!connectionProfile) { - // User cancelled - return undefined; - } // Get the list of databases now to validate that the connection is valid and re-prompt them if it isn't try { connectionUri = await vscodeMssqlApi.connect(connectionProfile); dbs = (await vscodeMssqlApi.listDatabases(connectionUri)) .filter(db => !constants.systemDbs.includes(db)); // Filter out system dbs } catch (err) { - // no-op, the mssql extension handles showing the error to the user. We'll just go - // back and prompt the user for a connection again + // The mssql extension handles showing the error to the user. Prompt the user + // for a new connection and then go and try getting the DBs again + connectionProfile = await vscodeMssqlApi.promptForConnection(true); + if (!connectionProfile) { + // User cancelled + return undefined; + } + } } + // Move the database for the given connection up to the top + if (connectionProfile.database && connectionProfile.database !== constants.master) { + const index = dbs.indexOf(connectionProfile.database); + if (index >= 0) { + dbs.splice(index, 1); + } + dbs.unshift(connectionProfile.database); + } + // 2. Select database const selectedDatabase = await vscode.window.showQuickPick( dbs, diff --git a/extensions/sql-database-projects/src/typings/vscode-mssql.d.ts b/extensions/sql-database-projects/src/typings/vscode-mssql.d.ts index 66349feb05..4d7c826581 100644 --- a/extensions/sql-database-projects/src/typings/vscode-mssql.d.ts +++ b/extensions/sql-database-projects/src/typings/vscode-mssql.d.ts @@ -4,6 +4,9 @@ *--------------------------------------------------------------------------------------------*/ declare module 'vscode-mssql' { + + import * as vscode from 'vscode'; + /** * Covers defining what the vscode-mssql extension exports to other extensions * @@ -51,6 +54,15 @@ declare module 'vscode-mssql' { * @returns The list of database names */ listDatabases(connectionUri: string): Promise; + + /** + * Gets the database name for the node - which is the database name of the connection for a server node, the database name + * for nodes at or under a database node or a default value if it's neither of those. + * @param node The node to get the database name of + * @returns The database name + */ + getDatabaseNameFromTreeNode(node: ITreeNodeInfo): string; + } /** @@ -490,4 +502,29 @@ declare module 'vscode-mssql' { defaultDeploymentOptions: DeploymentOptions; } + export interface ITreeNodeInfo extends vscode.TreeItem { + readonly connectionInfo: IConnectionInfo; + nodeType: string; + metadata: ObjectMetadata; + parentNode: ITreeNodeInfo; + } + + export const enum MetadataType { + Table = 0, + View = 1, + SProc = 2, + Function = 3 + } + + export interface ObjectMetadata { + metadataType: MetadataType; + + metadataTypeName: string; + + urn: string; + + name: string; + + schema: string; + } }