Add DB Reference quickpick dialog (#16601)

* Add DB Reference quickpick dialog

* pr comments & cleanup
This commit is contained in:
Charles Gagnon
2021-08-09 12:46:53 -07:00
committed by GitHub
parent c22589574c
commit b2ff8162e6
7 changed files with 342 additions and 41 deletions

View File

@@ -165,7 +165,7 @@ export class AddDatabaseReferenceDialog {
} else if (this.currentReferenceType === ReferenceType.systemDb) {
referenceSettings = {
databaseName: <string>this.databaseNameTextbox?.value,
systemDb: <string>this.systemDatabaseDropdown?.value === constants.master ? SystemDatabase.master : SystemDatabase.msdb,
systemDb: getSystemDatabase(<string>this.systemDatabaseDropdown?.value),
suppressMissingDependenciesErrors: <boolean>this.suppressMissingDependenciesErrorsCheckbox?.checked
};
} else { // this.currentReferenceType === ReferenceType.dacpac
@@ -192,7 +192,7 @@ export class AddDatabaseReferenceDialog {
this.projectRadioButton = this.view!.modelBuilder.radioButton()
.withProps({
name: 'referenceType',
label: constants.projectRadioButtonTitle
label: constants.projectLabel
}).component();
this.projectRadioButton.onDidClick(() => {
@@ -202,7 +202,7 @@ export class AddDatabaseReferenceDialog {
this.systemDatabaseRadioButton = this.view!.modelBuilder.radioButton()
.withProps({
name: 'referenceType',
label: constants.systemDatabaseRadioButtonTitle
label: constants.systemDatabase
}).component();
this.systemDatabaseRadioButton.onDidClick(() => {
@@ -307,7 +307,7 @@ export class AddDatabaseReferenceDialog {
private createSystemDatabaseDropdown(): azdataType.FormComponent {
this.systemDatabaseDropdown = this.view!.modelBuilder.dropDown().withProps({
values: [constants.master, constants.msdb],
values: getSystemDbOptions(this.project),
ariaLabel: constants.databaseNameLabel
}).component();
@@ -315,11 +315,6 @@ export class AddDatabaseReferenceDialog {
this.setDefaultDatabaseValues();
});
// only master is a valid system db reference for projects targetting Azure and DW
if (this.project.getProjectTargetVersion().toLowerCase().includes('azure') || this.project.getProjectTargetVersion().toLowerCase().includes('dw')) {
this.systemDatabaseDropdown.values?.splice(1);
}
return {
component: this.systemDatabaseDropdown,
title: constants.databaseNameLabel
@@ -329,7 +324,7 @@ export class AddDatabaseReferenceDialog {
private createDacpacTextbox(): azdataType.FormComponent {
this.dacpacTextbox = this.view!.modelBuilder.inputBox().withProps({
ariaLabel: constants.dacpacText,
placeHolder: constants.dacpacPlaceholder,
placeHolder: constants.selectDacpac,
width: '400px'
}).component();
@@ -351,25 +346,14 @@ export class AddDatabaseReferenceDialog {
private createLoadDacpacButton(): azdataType.ButtonComponent {
const loadDacpacButton = this.view!.modelBuilder.button().withProps({
ariaLabel: constants.loadDacpacButton,
ariaLabel: constants.selectDacpac,
iconPath: IconPathHelper.folder_blue,
height: '18px',
width: '18px'
}).component();
loadDacpacButton.onDidClick(async () => {
let fileUris = await vscode.window.showOpenDialog(
{
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
defaultUri: vscode.workspace.workspaceFolders ? (vscode.workspace.workspaceFolders as vscode.WorkspaceFolder[])[0].uri : undefined,
openLabel: constants.selectString,
filters: {
[constants.dacpacFiles]: ['dacpac'],
}
}
);
let fileUris = await promptDacpacLocation();
if (!fileUris || fileUris.length === 0) {
return;
@@ -383,7 +367,7 @@ export class AddDatabaseReferenceDialog {
private createLocationDropdown(): azdataType.FormComponent {
this.locationDropdown = this.view!.modelBuilder.dropDown().withProps({
ariaLabel: constants.locationDropdown,
ariaLabel: constants.location,
values: this.currentReferenceType === ReferenceType.systemDb ? constants.systemDbLocationDropdownValues : constants.locationDropdownValues
}).component();
@@ -397,7 +381,7 @@ export class AddDatabaseReferenceDialog {
return {
component: this.locationDropdown,
title: constants.locationDropdown
title: constants.location
};
}
@@ -630,3 +614,31 @@ export class AddDatabaseReferenceDialog {
return !!this.databaseNameTextbox?.value && !!this.serverNameTextbox?.value && !!this.serverVariableTextbox?.value;
}
}
export function getSystemDbOptions(project: Project): string[] {
// only master is a valid system db reference for projects targeting Azure and DW
if (project.getProjectTargetVersion().toLowerCase().includes('azure') || project.getProjectTargetVersion().toLowerCase().includes('dw')) {
return [constants.master];
}
return [constants.master, constants.msdb];
}
export function getSystemDatabase(name: string): SystemDatabase {
return name === constants.master ? SystemDatabase.master : SystemDatabase.msdb;
}
export async function promptDacpacLocation(): Promise<vscode.Uri[] | undefined> {
return await vscode.window.showOpenDialog(
{
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
defaultUri: vscode.workspace.workspaceFolders ? (vscode.workspace.workspaceFolders as vscode.WorkspaceFolder[])[0].uri : undefined,
openLabel: constants.selectString,
title: constants.selectDacpac,
filters: {
[constants.dacpacFiles]: ['dacpac'],
}
}
);
}

View File

@@ -0,0 +1,277 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import path = require('path');
import * as vscode from 'vscode';
import * as constants from '../common/constants';
import { getSqlProjectsInWorkspace, isValidSqlCmdVariableName, removeSqlCmdVariableFormatting } from '../common/utils';
import { AddDatabaseReferenceSettings } from '../controllers/projectController';
import { IDacpacReferenceSettings, IProjectReferenceSettings, ISystemDatabaseReferenceSettings } from '../models/IDatabaseReferenceSettings';
import { Project } from '../models/project';
import { getSystemDatabase, getSystemDbOptions, promptDacpacLocation } from './addDatabaseReferenceDialog';
interface DbServerValues {
dbName?: string,
dbVariable?: string,
serverName?: string,
serverVariable?: string
}
/**
* Create flow for adding a database reference 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 addDatabaseReferenceQuickpick(project: Project): Promise<AddDatabaseReferenceSettings | undefined> {
const otherProjectsInWorkspace = (await getSqlProjectsInWorkspace()).filter(p => p.fsPath !== project.projectFilePath);
// 1. Prompt for reference type
// Only show project option if we have at least one other project in the workspace
const referenceTypes = otherProjectsInWorkspace.length > 0 ?
[constants.projectLabel, constants.systemDatabase, constants.dacpacText] :
[constants.systemDatabase, constants.dacpacText];
const referenceType = await vscode.window.showQuickPick(
referenceTypes,
{ title: constants.referenceType, ignoreFocusOut: true });
if (!referenceType) {
// User cancelled
return undefined;
}
switch (referenceType) {
case constants.projectLabel:
return addProjectReference(otherProjectsInWorkspace);
case constants.systemDatabase:
return addSystemDatabaseReference(project);
case constants.dacpacText:
return addDacpacReference();
default:
console.log(`Unknown reference type ${referenceType}`);
return undefined;
}
}
async function addProjectReference(otherProjectsInWorkspace: vscode.Uri[]): Promise<IProjectReferenceSettings | undefined> {
// (steps continued from addDatabaseReferenceQuickpick)
// 2. Prompt database project
const otherProjectQuickpickItems: (vscode.QuickPickItem & { uri: vscode.Uri })[] = otherProjectsInWorkspace.map(p => {
return {
label: path.parse(p.fsPath).name,
uri: p
};
});
const selectedProject = await vscode.window.showQuickPick(
otherProjectQuickpickItems,
{ title: constants.databaseProject, ignoreFocusOut: true, });
if (!selectedProject) {
return;
}
// 3. Prompt location
const location = await promptLocation();
if (!location) {
// User cancelled
return;
}
const referenceSettings: IProjectReferenceSettings = {
projectName: selectedProject.label,
projectGuid: '',
projectRelativePath: undefined,
databaseName: undefined,
databaseVariable: undefined,
serverName: undefined,
serverVariable: undefined,
suppressMissingDependenciesErrors: false
};
const dbServerValues = await promptDbServerValues(location, selectedProject.label);
if (!dbServerValues) {
// User cancelled
return;
}
referenceSettings.databaseName = dbServerValues.dbName;
referenceSettings.databaseVariable = dbServerValues.dbVariable;
referenceSettings.serverName = dbServerValues.serverName;
referenceSettings.serverVariable = dbServerValues.serverVariable;
// 7. Prompt suppress unresolved ref errors
const suppressErrors = await promptSuppressUnresolvedRefErrors();
referenceSettings.suppressMissingDependenciesErrors = suppressErrors;
return referenceSettings;
}
async function addSystemDatabaseReference(project: Project): Promise<ISystemDatabaseReferenceSettings | undefined> {
// (steps continued from addDatabaseReferenceQuickpick)
// 2. Prompt System DB
const selectedSystemDb = await vscode.window.showQuickPick(
getSystemDbOptions(project),
{ title: constants.systemDatabase, ignoreFocusOut: true, });
if (!selectedSystemDb) {
// User cancelled
return undefined;
}
// 3. Prompt DB name
const dbName = await promptDbName(selectedSystemDb);
// 4. Prompt suppress unresolved ref errors
const suppressErrors = await promptSuppressUnresolvedRefErrors();
return {
databaseName: dbName,
systemDb: getSystemDatabase(selectedSystemDb),
suppressMissingDependenciesErrors: suppressErrors
};
}
async function addDacpacReference(): Promise<IDacpacReferenceSettings | undefined> {
// (steps continued from addDatabaseReferenceQuickpick)
// 2. Prompt for location
const location = await promptLocation();
if (!location) {
// User cancelled
return undefined;
}
// 3. Prompt for dacpac location
// Show quick pick with just browse option to give user context about what the file dialog is for (since that doesn't always have a title)
const browseSelected = await vscode.window.showQuickPick(
[constants.browseEllipsis],
{ title: constants.selectDacpac, ignoreFocusOut: true });
if (!browseSelected) {
return undefined;
}
const dacPacLocation = (await promptDacpacLocation())?.[0];
if (!dacPacLocation) {
// User cancelled
return undefined;
}
// 4. Prompt for db/server values
const dbServerValues = await promptDbServerValues(location, path.parse(dacPacLocation.fsPath).name);
if (!dbServerValues) {
// User cancelled
return;
}
// 5. Prompt suppress unresolved ref errors
const suppressErrors = await promptSuppressUnresolvedRefErrors();
return {
databaseName: dbServerValues.dbName,
dacpacFileLocation: dacPacLocation,
databaseVariable: removeSqlCmdVariableFormatting(dbServerValues.dbVariable),
serverName: dbServerValues.serverName,
serverVariable: removeSqlCmdVariableFormatting(dbServerValues.serverVariable),
suppressMissingDependenciesErrors: suppressErrors
};
}
async function promptLocation(): Promise<string | undefined> {
return vscode.window.showQuickPick(
constants.locationDropdownValues,
{ title: constants.location, ignoreFocusOut: true, });
}
async function promptDbName(defaultValue: string): Promise<string | undefined> {
return vscode.window.showInputBox(
{
title: constants.databaseName,
value: defaultValue,
validateInput: (value) => {
return value ? undefined : constants.nameMustNotBeEmpty;
},
ignoreFocusOut: true
});
}
async function promptDbVar(defaultValue: string): Promise<string> {
return await vscode.window.showInputBox(
{
title: constants.databaseVariable,
value: defaultValue,
validateInput: (value: string) => {
return isValidSqlCmdVariableName(value) ? '' : constants.notValidVariableName(value);
},
ignoreFocusOut: true
}) ?? '';
}
async function promptServerName(): Promise<string | undefined> {
return vscode.window.showInputBox(
{
title: constants.serverName,
value: constants.otherServer,
validateInput: (value) => {
return value ? undefined : constants.nameMustNotBeEmpty;
},
ignoreFocusOut: true
});
}
async function promptServerVar(): Promise<string> {
return await vscode.window.showInputBox(
{
title: constants.serverVariable,
value: constants.otherSeverVariable,
validateInput: (value: string) => {
return isValidSqlCmdVariableName(value) ? '' : constants.notValidVariableName(value);
},
ignoreFocusOut: true
}) ?? '';
}
async function promptSuppressUnresolvedRefErrors(): Promise<boolean> {
const selectedOption = await vscode.window.showQuickPick(
[constants.noStringDefault, constants.yesString],
{ title: constants.suppressMissingDependenciesErrors, ignoreFocusOut: true, });
return selectedOption === constants.yesString ? true : false;
}
async function promptDbServerValues(location: string, defaultDbName: string): Promise<DbServerValues | undefined> {
const ret: DbServerValues = {};
// Only prompt db values if the location is on a different db/server
if (location !== constants.sameDatabase) {
// 4. Prompt database name
const dbName = await promptDbName(defaultDbName);
if (!dbName) {
// User cancelled
return undefined;
}
ret.dbName = dbName;
// 5. Prompt db var
const dbVar = await promptDbVar(dbName);
// DB Variable is optional so treat escape as skipping it (not cancel in this case)
ret.dbVariable = dbVar;
}
// Only prompt server values if location is different server
if (location === constants.differentDbDifferentServer) {
// 5. Prompt server name
const serverName = await promptServerName();
if (!serverName) {
// User cancelled
return undefined;
}
ret.serverName = serverName;
// 6. Prompt server var
const serverVar = await promptServerVar();
if (!serverVar) {
// User cancelled
return undefined;
}
ret.serverVariable = serverVar;
}
return ret;
}

View File

@@ -269,7 +269,7 @@ export class CreateProjectFromDatabaseDialog {
this.projectLocationTextBox = view.modelBuilder.inputBox().withProps({
value: '',
ariaLabel: constants.projectLocationLabel,
ariaLabel: constants.location,
placeHolder: constants.projectLocationPlaceholderText,
width: cssStyles.createProjectFromDatabaseTextboxWidth
}).component();
@@ -280,7 +280,7 @@ export class CreateProjectFromDatabaseDialog {
});
const projectLocationLabel = view.modelBuilder.text().withProps({
value: constants.projectLocationLabel,
value: constants.location,
requiredIndicator: true,
width: cssStyles.createProjectFromDatabaseLabelWidth
}).component();

View File

@@ -192,7 +192,6 @@ export async function launchPublishDatabaseQuickpick(project: Project, projectCo
return;
}
// TODO@chgagnon: Get deployment options
// 6. Generate script/publish
let settings: IDeploySettings = {
databaseName: databaseName,