Files
azuredatastudio/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceQuickpick.ts
Kim Santiago 934d8ff8fa Add nupkg database reference option for sql projects in vscode (#22882)
* add nupkg db ref option in vscode

* add telemetry for nupkg db ref

* update comment
2023-04-28 14:57:22 -07:00

380 lines
12 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* 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, getSystemDatabase, validateSqlCmdVariableName } from '../common/utils';
import { DbServerValues, populateResultWithVars } from './utils';
import { AddDatabaseReferenceSettings } from '../controllers/projectController';
import { IDacpacReferenceSettings, INugetPackageReferenceSettings, IProjectReferenceSettings, ISystemDatabaseReferenceSettings } from '../models/IDatabaseReferenceSettings';
import { Project } from '../models/project';
import { getSystemDbOptions, promptDacpacLocation } from './addDatabaseReferenceDialog';
import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry';
import { ProjectType } from 'mssql';
/**
* Create flow for adding a database reference using only VS Code-native APIs such as QuickPick
*/
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];
// only add nupkg database reference option if project is SDK-style
if (project.sqlProjStyle === ProjectType.SdkStyle) {
referenceTypes.push(constants.nupkgText);
}
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(project);
case constants.nupkgText:
return addNupkgReference();
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;
}
populateResultWithVars(referenceSettings, dbServerValues);
// 7. Prompt suppress unresolved ref errors
const suppressErrors = await promptSuppressUnresolvedRefErrors();
referenceSettings.suppressMissingDependenciesErrors = suppressErrors;
TelemetryReporter.createActionEvent(TelemetryViews.ProjectTree, TelemetryActions.addDatabaseReference)
.withAdditionalProperties({ referenceType: constants.projectLabel })
.send();
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();
TelemetryReporter.createActionEvent(TelemetryViews.ProjectTree, TelemetryActions.addDatabaseReference)
.withAdditionalProperties({ referenceType: constants.systemDatabase })
.send();
return {
databaseVariableLiteralValue: dbName,
systemDb: getSystemDatabase(selectedSystemDb),
suppressMissingDependenciesErrors: suppressErrors
};
}
async function addDacpacReference(project: Project): 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)
let dacPacLocation;
while (!dacPacLocation) {
const browseSelected = await vscode.window.showQuickPick(
[constants.browseEllipsisWithIcon],
{
title: constants.selectDacpac,
ignoreFocusOut: true,
placeHolder: constants.dacpacMustBeOnSameDrive
});
if (!browseSelected) {
return undefined;
}
dacPacLocation = (await promptDacpacLocation())?.[0];
if (!dacPacLocation) {
// User cancelled
return undefined;
}
// only support adding dacpacs that are on the same drive as the sqlproj
const projectDrive = path.parse(project.projectFilePath).root;
const dacpacDrive = path.parse(dacPacLocation.fsPath).root;
if (projectDrive !== dacpacDrive) {
void vscode.window.showErrorMessage(constants.dacpacNotOnSameDrive(project.projectFilePath));
// set dacPacLocation to undefined so that the browse quickpick will show again
dacPacLocation = 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();
// 6. Construct result
const referenceSettings: IDacpacReferenceSettings = {
dacpacFileLocation: dacPacLocation,
suppressMissingDependenciesErrors: suppressErrors
};
populateResultWithVars(referenceSettings, dbServerValues);
TelemetryReporter.createActionEvent(TelemetryViews.ProjectTree, TelemetryActions.addDatabaseReference)
.withAdditionalProperties({ referenceType: constants.dacpacText })
.send();
return referenceSettings;
}
async function addNupkgReference(): Promise<INugetPackageReferenceSettings | undefined> {
// (steps continued from addDatabaseReferenceQuickpick)
// 2. Prompt for location
const location = await promptLocation();
if (!location) {
// User cancelled
return undefined;
}
// 3. Prompt for NuGet package name
const nupkgName = await vscode.window.showInputBox(
{
title: constants.nupkgText,
placeHolder: constants.nupkgNamePlaceholder,
validateInput: (value) => {
return value ? undefined : constants.nameMustNotBeEmpty;
},
ignoreFocusOut: true
});
if (!nupkgName) {
// User cancelled
return undefined;
}
// 4. Prompt for NuGet package version
const nupkgVersion = await vscode.window.showInputBox(
{
title: constants.version,
placeHolder: constants.versionPlaceholder,
validateInput: (value) => {
return value ? undefined : constants.versionMustNotBeEmpty;
},
ignoreFocusOut: true
});
if (!nupkgVersion) {
// User cancelled
return undefined;
}
// 5. Prompt for db/server values
const dbServerValues = await promptDbServerValues(location, path.parse(nupkgName).name);
if (!dbServerValues) {
// User cancelled
return;
}
// 6. Prompt suppress unresolved ref errors
const suppressErrors = await promptSuppressUnresolvedRefErrors();
// 7. Construct result
const referenceSettings: INugetPackageReferenceSettings = {
packageName: nupkgName,
packageVersion: nupkgVersion,
suppressMissingDependenciesErrors: suppressErrors
};
populateResultWithVars(referenceSettings, dbServerValues);
TelemetryReporter.createActionEvent(TelemetryViews.ProjectTree, TelemetryActions.addDatabaseReference)
.withAdditionalProperties({ referenceType: constants.nupkgText })
.send();
return referenceSettings;
}
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 validateSqlCmdVariableName(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 validateSqlCmdVariableName(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;
}