Sql Proj: Adding a new sqlproj template for SQL dbs (#18636)

This commit is contained in:
Leila Lali
2022-03-29 09:06:56 -07:00
committed by GitHub
parent 25b8137a45
commit 459fab256e
28 changed files with 223 additions and 62 deletions

View File

@@ -26,17 +26,21 @@ export const sqlProjectSdkVersion = '0.1.3-preview';
// Project Provider
export const emptySqlDatabaseProjectTypeId = 'EmptySqlDbProj';
export const emptyProjectTypeDisplayName = localize('emptyProjectTypeDisplayName', "SQL Database");
export const emptyProjectTypeDescription = localize('emptyProjectTypeDescription', "Develop and publish schemas for SQL databases starting from an empty project");
export const emptyProjectTypeDisplayName = localize('emptyProjectTypeDisplayName', "SQL Server Database");
export const emptyProjectTypeDescription = localize('emptyProjectTypeDescription', "Develop and publish schemas for SQL Server databases starting from an empty project");
export const edgeSqlDatabaseProjectTypeId = 'SqlDbEdgeProj';
export const edgeProjectTypeDisplayName = localize('edgeProjectTypeDisplayName', "SQL Edge");
export const edgeProjectTypeDescription = localize('edgeProjectTypeDescription', "Start with the core pieces to develop and publish schemas for SQL Edge");
export const edgeProjectTypeDisplayName = localize('edgeProjectTypeDisplayName', "Azure SQL Edge Database");
export const edgeProjectTypeDescription = localize('edgeProjectTypeDescription', "Start with the core pieces to develop and publish schemas for Azure SQL Edge Database");
export const emptySqlDatabaseSdkProjectTypeId = 'EmptySqlDbSdkProj';
export const emptySdkProjectTypeDisplayName = localize('emptySdkProjectTypeDisplayName', "SQL Database (SDK)");
export const emptySdkProjectTypeDescription = localize('emptySdkProjectTypeDescription', "Develop and publish schemas for SQL databases with Microsoft.Build.Sql (preview), starting from an empty SDK-style project.");
export const emptyAzureDbSqlDatabaseProjectTypeId = 'EmptyAzureSqlDbProj';
export const emptyAzureDbProjectTypeDisplayName = localize('emptyAzureDbProjectTypeDisplayName', "Azure SQL Database");
export const emptyAzureDbProjectTypeDescription = localize('emptyAzureDbProjectTypeDescription', "Develop and publish schemas for Azure SQL Database starting from an empty project");
// Dashboard
export const addItemAction = localize('addItemAction', "Add Item");
export const schemaCompareAction = localize('schemaCompareAction', "Schema Compare");
@@ -199,7 +203,7 @@ export const runningDockerMessage = localize('runningDockerMessage', "Running th
export function dockerNotRunningError(error: string) { return localize('dockerNotRunningError', "Failed to verify docker. Please make sure docker is installed and running. Error: '{0}'", error || ''); }
export const dockerContainerNotRunningErrorMessage = localize('dockerContainerNotRunningErrorMessage', "Docker container is not running");
export const dockerContainerFailedToRunErrorMessage = localize('dockerContainerFailedToRunErrorMessage', "Failed to run the docker container");
export const connectingToSqlServerOnDockerMessage = localize('connectingToSqlServerOnDockerMessage', "Connecting to SQL Server on Docker");
export const connectingToSqlServerMessage = localize('connectingToSqlServerOnDockerMessage', "Connecting to SQL Server");
export const deployProjectFailedMessage = localize('deployProjectFailedMessage', "Failed to open a connection to the deployed database'");
export const containerAlreadyExistForProject = localize('containerAlreadyExistForProject', "Containers already exist for this project. Do you want to delete them before deploying a new one?");
export const checkoutOutputMessage = localize('checkoutOutputMessage', "Check output pane for more details");

View File

@@ -16,6 +16,7 @@ export class IconPathHelper {
public static databaseProject: IconPath;
public static colorfulSqlProject: IconPath;
public static sqlEdgeProject: IconPath;
public static azureSqlDbProject: IconPath;
public static dataSourceGroup: IconPath;
public static dataSourceSql: IconPath;
@@ -48,6 +49,7 @@ export class IconPathHelper {
IconPathHelper.databaseProject = IconPathHelper.makeIcon('databaseProject');
IconPathHelper.colorfulSqlProject = IconPathHelper.makeIcon('colorfulSqlProject', true);
IconPathHelper.sqlEdgeProject = IconPathHelper.makeIcon('sqlEdgeProject', true);
IconPathHelper.azureSqlDbProject = IconPathHelper.makeIcon('azure', true);
IconPathHelper.dataSourceGroup = IconPathHelper.makeIcon('dataSourceGroup');
IconPathHelper.dataSourceSql = IconPathHelper.makeIcon('dataSource-sql');

View File

@@ -8,6 +8,7 @@ import * as vscode from 'vscode';
import * as vscodeMssql from 'vscode-mssql';
import * as mssql from 'mssql';
import * as templates from '../templates/templates';
import * as projectAssets from '../projectProvider/projectAssets';
import * as path from 'path';
import { ProjectsController } from './projectController';
@@ -88,6 +89,7 @@ export default class MainController implements vscode.Disposable {
IconPathHelper.setExtensionContext(this.extensionContext);
await templates.loadTemplates(path.join(this.context.extensionPath, 'resources', 'templates'));
projectAssets.loadAssets(path.join(this.context.extensionPath, 'resources', 'projectAssets'));
}
public dispose(): void {

View File

@@ -11,6 +11,7 @@ import * as utils from '../common/utils';
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
import * as templates from '../templates/templates';
import * as vscode from 'vscode';
import * as fse from 'fs-extra';
import type * as azdataType from 'azdata';
import * as dataworkspace from 'dataworkspace';
import type * as mssqlVscode from 'vscode-mssql';
@@ -46,6 +47,7 @@ import { addDatabaseReferenceQuickpick } from '../dialogs/addDatabaseReferenceQu
import { IDeployProfile } from '../models/deploy/deployProfile';
import { EntryType, FileProjectEntry, IDatabaseReferenceProjectEntry, SqlProjectReferenceProjectEntry } from '../models/projectEntry';
import { UpdateProjectAction, UpdateProjectDataModel } from '../models/api/updateProject';
import { targetPlatformToAssets } from '../projectProvider/projectAssets';
const maxTableLength = 10;
@@ -169,10 +171,12 @@ export class ProjectsController {
throw new Error(constants.invalidTargetPlatform(creationParams.targetPlatform, Array.from(constants.targetPlatformToVersion.keys())));
}
const targetPlatform = creationParams.targetPlatform ? constants.targetPlatformToVersion.get(creationParams.targetPlatform)! : constants.defaultDSP;
const macroDict: Record<string, string> = {
'PROJECT_NAME': creationParams.newProjName,
'PROJECT_GUID': creationParams.projectGuid ?? UUID.generateUuid().toUpperCase(),
'PROJECT_DSP': creationParams.targetPlatform ? constants.targetPlatformToVersion.get(creationParams.targetPlatform)! : constants.defaultDSP
'PROJECT_DSP': targetPlatform
};
let newProjFileContents = creationParams.sdkStyle ? templates.macroExpansion(templates.newSdkSqlProjectTemplate, macroDict) : templates.macroExpansion(templates.newSqlProjectTemplate, macroDict);
@@ -189,9 +193,24 @@ export class ProjectsController {
throw new Error(constants.projectAlreadyExists(newProjFileName, path.parse(newProjFilePath).dir));
}
await fs.mkdir(path.dirname(newProjFilePath), { recursive: true });
const projectFolderPath = path.dirname(newProjFilePath);
await fs.mkdir(projectFolderPath, { recursive: true });
await fs.writeFile(newProjFilePath, newProjFileContents);
// Copy project readme
if (targetPlatformToAssets?.has(targetPlatform) && (targetPlatformToAssets?.get(targetPlatform)?.readmeFolder)) {
const readmeFolder = targetPlatformToAssets.get(targetPlatform)?.readmeFolder;
if (readmeFolder) {
const readmeFile = path.join(readmeFolder, 'README.md');
const folderExists = await utils.exists(readmeFile);
if (folderExists) {
await fs.copyFile(readmeFile, path.join(projectFolderPath, 'README.md'));
await fse.copy(path.join(readmeFolder, 'assets'), path.join(projectFolderPath, 'assets'));
}
}
}
await this.addTemplateFiles(newProjFilePath, creationParams.projectTypeId);
return newProjFilePath;

View File

@@ -7,7 +7,7 @@ import * as vscode from 'vscode';
import * as constants from '../common/constants';
import * as utils from '../common/utils';
import * as uiUtils from './utils';
import { AppSettingType, IDeployAppIntegrationProfile, IDeployProfile, ILocalDbSetting } from '../models/deploy/deployProfile';
import { AppSettingType, DockerImageInfo, IDeployAppIntegrationProfile, IDeployProfile, ILocalDbSetting } from '../models/deploy/deployProfile';
import { Project } from '../models/project';
import { getPublishDatabaseSettings } from './publishDatabaseQuickpick';
import * as path from 'path';
@@ -70,10 +70,8 @@ export async function launchDeployAppIntegrationQuickpick(project: Project): Pro
};
}
async function launchEulaQuickPick(baseImage: string): Promise<boolean> {
async function launchEulaQuickPick(imageInfo: DockerImageInfo | undefined): Promise<boolean> {
let eulaAccepted: boolean = false;
const baseImages = uiUtils.getDockerBaseImages();
const imageInfo = baseImages.find(x => x.name === baseImage);
const agreementInfo = imageInfo?.agreementInfo;
if (agreementInfo) {
const openEulaButton: vscode.QuickInputButton = {
@@ -165,9 +163,9 @@ export async function launchPublishToDockerContainerQuickpick(project: Project):
return undefined;
}
const baseImages = uiUtils.getDockerBaseImages();
const baseImages = uiUtils.getDockerBaseImages(project.getProjectTargetVersion());
const baseImage = await vscode.window.showQuickPick(
baseImages.map(x => x.name),
baseImages.map(x => x.displayName),
{ title: constants.selectBaseImage(name), ignoreFocusOut: true });
// Return when user hits escape
@@ -175,20 +173,19 @@ export async function launchPublishToDockerContainerQuickpick(project: Project):
return undefined;
}
const eulaAccepted = await launchEulaQuickPick(baseImage);
const imageInfo = baseImages.find(x => x.displayName === baseImage);
const eulaAccepted = await launchEulaQuickPick(imageInfo);
if (!eulaAccepted) {
return undefined;
}
const imageInfo = baseImages.find(x => x.name === baseImage);
localDbSetting = {
serverName: constants.defaultLocalServerName,
userName: constants.defaultLocalServerAdminName,
dbName: project.projectFileName,
password: password,
port: +portNumber,
dockerBaseImage: baseImage,
dockerBaseImage: imageInfo?.name || '',
dockerBaseImageEula: imageInfo?.agreementInfo?.link?.url || ''
};

View File

@@ -230,7 +230,7 @@ export class PublishDatabaseDialog {
await this.publish!(this.project, settings);
} else {
const dockerBaseImage = this.getBaseDockerImageName();
const baseImages = getDockerBaseImages();
const baseImages = getDockerBaseImages(this.project.getProjectTargetVersion());
const imageInfo = baseImages.find(x => x.name === dockerBaseImage);
const settings: IDeployProfile = {
localDbSetting: {
@@ -315,7 +315,7 @@ export class PublishDatabaseDialog {
}
public getBaseDockerImageName(): string {
return <string>this.baseDockerImageDropDown?.value ?? '';
return (<azdataType.CategoryValue>this.baseDockerImageDropDown?.value)?.name ?? '';
}
public getDefaultDatabaseName(): string {
@@ -586,9 +586,10 @@ export class PublishDatabaseDialog {
});
const serverConfirmPasswordRow = this.createFormRow(view, constants.confirmServerPassword(name), this.serverConfigAdminPasswordTextBox);
const baseImages = getDockerBaseImages();
const baseImages = getDockerBaseImages(this.project.getProjectTargetVersion());
const baseImagesValues: azdataType.CategoryValue[] = baseImages.map(x => { return { name: x.name, displayName: x.displayName }; });
this.baseDockerImageDropDown = view.modelBuilder.dropDown().withProps({
values: baseImages.map(x => x.name),
values: baseImagesValues,
ariaLabel: constants.baseDockerImage(name),
width: cssStyles.publishDialogTextboxWidth,
enabled: true
@@ -612,7 +613,7 @@ export class PublishDatabaseDialog {
if (this.eulaCheckBox) {
this.eulaCheckBox.checked = false;
}
const baseImage = getDockerBaseImages().find(x => x.name === this.baseDockerImageDropDown?.value);
const baseImage = getDockerBaseImages(this.project.getProjectTargetVersion()).find(x => x.name === (<azdataType.CategoryValue>this.baseDockerImageDropDown?.value).name);
if (baseImage?.agreementInfo.link) {
const text = view.modelBuilder.text().withProps({
value: constants.eulaAgreementTemplate,

View File

@@ -38,34 +38,59 @@ export function getPublishServerName(target: string): string {
return target === constants.targetPlatformToVersion.get(SqlTargetPlatform.sqlAzure) ? constants.AzureSqlServerName : constants.SqlServerName;
}
export function getDockerBaseImages(): DockerImageInfo[] {
return [
{
name: `${constants.sqlServerDockerRegistry}/${constants.sqlServerDockerRepository}:2017-latest`,
agreementInfo: {
link: {
text: constants.eulaAgreementTitle,
url: constants.sqlServerEulaLink,
}
}
},
{
export function getDockerBaseImages(target: string): DockerImageInfo[] {
if (target === constants.targetPlatformToVersion.get(SqlTargetPlatform.sqlAzure)) {
return [{
name: `${constants.sqlServerDockerRegistry}/${constants.sqlServerDockerRepository}:2019-latest`,
displayName: SqlTargetPlatform.sqlServer2019,
agreementInfo: {
link: {
text: constants.eulaAgreementTitle,
url: constants.sqlServerEulaLink,
}
}
},
{
}, {
name: `${constants.sqlServerDockerRegistry}/${constants.azureSqlEdgeDockerRepository}:latest`,
displayName: SqlTargetPlatform.sqlEdge,
agreementInfo: {
link: {
text: constants.edgeEulaAgreementTitle,
url: constants.sqlServerEdgeEulaLink,
}
}
},
];
}];
} else {
return [
{
name: `${constants.sqlServerDockerRegistry}/${constants.sqlServerDockerRepository}:2017-latest`,
displayName: SqlTargetPlatform.sqlServer2017,
agreementInfo: {
link: {
text: constants.eulaAgreementTitle,
url: constants.sqlServerEulaLink,
}
}
},
{
name: `${constants.sqlServerDockerRegistry}/${constants.sqlServerDockerRepository}:2019-latest`,
displayName: SqlTargetPlatform.sqlServer2019,
agreementInfo: {
link: {
text: constants.eulaAgreementTitle,
url: constants.sqlServerEulaLink,
}
}
},
{
name: `${constants.sqlServerDockerRegistry}/${constants.azureSqlEdgeDockerRepository}:latest`,
displayName: SqlTargetPlatform.sqlEdge,
agreementInfo: {
link: {
text: constants.edgeEulaAgreementTitle,
url: constants.sqlServerEdgeEulaLink,
}
}
},
];
}
}

View File

@@ -35,6 +35,7 @@ export interface ILocalDbSetting {
export interface DockerImageInfo {
name: string,
displayName: string,
agreementInfo: AgreementInfo
}
export interface AgreementInfo {

View File

@@ -310,7 +310,7 @@ export class DeployService {
public async getConnection(profile: ILocalDbSetting, saveConnectionAndPassword: boolean, database: string): Promise<string | undefined> {
const getAzdataApi = await utils.getAzdataApi();
let connection = await utils.retry(
constants.connectingToSqlServerOnDockerMessage,
constants.connectingToSqlServerMessage,
async () => {
return await this.connectToDatabase(profile, saveConnectionAndPassword, database);
},

View File

@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
export interface ProjectAssets {
readmeFolder?: string
}
export let targetPlatformToAssets: Map<string, ProjectAssets>;
export function loadAssets(assetsFolderPath: string) {
targetPlatformToAssets = new Map<string, ProjectAssets>([
['AzureV12', {
readmeFolder: path.join(assetsFolderPath, 'AzureV12')
}],
]);
}

View File

@@ -35,26 +35,38 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide
* Gets the supported project types
*/
get supportedProjectTypes(): dataworkspace.IProjectType[] {
return [{
id: constants.emptySqlDatabaseProjectTypeId,
projectFileExtension: constants.sqlprojExtension.replace(/\./g, ''),
displayName: constants.emptyProjectTypeDisplayName,
description: constants.emptyProjectTypeDescription,
icon: IconPathHelper.colorfulSqlProject,
targetPlatforms: Array.from(constants.targetPlatformToVersion.keys()),
defaultTargetPlatform: constants.defaultTargetPlatform,
sdkStyleOption: true,
sdkStyleLearnMoreUrl: constants.sdkLearnMoreUrl
},
{
id: constants.edgeSqlDatabaseProjectTypeId,
projectFileExtension: constants.sqlprojExtension.replace(/\./g, ''),
displayName: constants.edgeProjectTypeDisplayName,
description: constants.edgeProjectTypeDescription,
icon: IconPathHelper.sqlEdgeProject,
sdkStyleOption: true,
sdkStyleLearnMoreUrl: constants.sdkLearnMoreUrl
}];
return [
{
id: constants.emptyAzureDbSqlDatabaseProjectTypeId,
projectFileExtension: constants.sqlprojExtension.replace(/\./g, ''),
displayName: constants.emptyAzureDbProjectTypeDisplayName,
description: constants.emptyAzureDbProjectTypeDescription,
defaultTargetPlatform: sqldbproj.SqlTargetPlatform.sqlAzure,
icon: IconPathHelper.azureSqlDbProject,
sdkStyleOption: true,
sdkStyleLearnMoreUrl: constants.sdkLearnMoreUrl
},
{
id: constants.emptySqlDatabaseProjectTypeId,
projectFileExtension: constants.sqlprojExtension.replace(/\./g, ''),
displayName: constants.emptyProjectTypeDisplayName,
description: constants.emptyProjectTypeDescription,
icon: IconPathHelper.colorfulSqlProject,
targetPlatforms: Array.from(constants.targetPlatformToVersion.keys()),
defaultTargetPlatform: constants.defaultTargetPlatform,
sdkStyleOption: true,
sdkStyleLearnMoreUrl: constants.sdkLearnMoreUrl
},
{
id: constants.edgeSqlDatabaseProjectTypeId,
projectFileExtension: constants.sqlprojExtension.replace(/\./g, ''),
displayName: constants.edgeProjectTypeDisplayName,
description: constants.edgeProjectTypeDescription,
icon: IconPathHelper.sqlEdgeProject,
sdkStyleOption: true,
sdkStyleLearnMoreUrl: constants.sdkLearnMoreUrl
}
];
}
/**
@@ -65,6 +77,13 @@ export class SqlDatabaseProjectProvider implements dataworkspace.IProjectProvide
* @returns Uri of the newly created project file
*/
async createProject(name: string, location: vscode.Uri, projectTypeId: string, targetPlatform?: sqldbproj.SqlTargetPlatform, sdkStyle: boolean = true): Promise<vscode.Uri> {
if (!targetPlatform) {
const projectType = this.supportedProjectTypes.find(x => x.id === projectTypeId);
if (projectType && projectType.defaultTargetPlatform) {
targetPlatform = projectType.defaultTargetPlatform as sqldbproj.SqlTargetPlatform;
}
}
const projectFile = await this.projectController.createNewProject({
newProjName: name,
folderUri: location,

View File

@@ -179,6 +179,7 @@ declare module 'sqldbproj' {
sqlServer2017 = 'SQL Server 2017',
sqlServer2019 = 'SQL Server 2019',
sqlAzure = 'Microsoft Azure SQL Database',
sqlDW = 'Microsoft Azure SQL Data Warehouse'
sqlDW = 'Microsoft Azure SQL Data Warehouse',
sqlEdge = 'Microsoft Azure SQL Edge'
}
}