Skip to content

Commit efcf9ba

Browse files
authored
Publish Dialog - Generate Script and Publish project to the database functionality (#20436)
* intial changes to load empty dialog * UI intial changes * advanced options added * basic UI final changes * UI changes with formwebviewcontroller * Basic UI final changes * removed Advanced related changes from the current set * converting const to static class * localized files * copilot comments adjusted * using new webview provider * updating the code * Adding publish dialog basic test file * adding target field changes * revert service instnace update and add loc fiels * addressing code review comments * adding tests that covers the target field * copilot review updates * little refactoring * updating the variable name * addressing code review comments * updating LOC and fixing test * merging the base pr changes and little bit of refactoring * confirm password validation with both pwd fields * addressing code review comments * undo double wrapping * rebase on intial setup PR with modification * loc updates * adding tests * checkbox alignment * adding main controller test to validate publish proj call * LOC update * fixing the issues caused due to resolve conflicts * fixing tests * refactoring * fixing build errors * moving to utils and constants * removing multi tests and adding clear comments for the methods * renaming profileName to publishprofileName for clear understanding * refactoring for options * intial commit to add select publish profile reducer that opens and selects the profile and updates the state * moving parseHtmlLabel method to react view utils file * save profile onclick and saving the xml, without connectionstring * format updates and save button logic update * adding tests for select and save profile * Using required constants throught the shared interfaces. * adding telemetry * exclude options are default to empty, add upon selection * reading publish.xml on selct profile * removing unsed method * Final changes for the profile section * LOC missing changes addding back * adding icon and disabling server input * connection work * LOC * adding conneciton string to the state and saving in profile * copilot review updates * reverting sqlprojservice to optional * combobox style fixes * saving selected DB * adding tests * preserving database options and value on target switch * fixing the stabilize conneciton switching issue * adding placeholder * LOC * making conn string empty for container * addressing copilot code suggestions * advanced drawer initial implementation * Loc * using onsuccessfulconneciton responce * Loc * removing dockerUtils file * update read sqlcmd variabled from profile using the xmlDom parser * typo updates and guard check added * connection null check * optimizing the validation logic * fixing the duplicate afterSetFormProperty methods * ignore option group added * LOC * moving method to Utils * logging when database connection fails * handle new connection details updates * test fixes * all are done except update profile and save profile changes with options and few tests * removed unnecessary advancedgroup options state * test updates * fixing the reset button and refactoring the comments * final changes * removing additional div element that disturbs the alignment * cleanup * final refactoring * test fix and redundant state update removed * updates and loc * genScript and pub initial changes * loc * genscript done, publish and refactor to test * publish works and test * final changes * validating and fixing tests * comment updates * fixing the api tests failing due to misusing of import * fixing the legacy project build scenario with buildDirectorypath * addressing review comments
1 parent 8846f47 commit efcf9ba

File tree

11 files changed

+853
-21
lines changed

11 files changed

+853
-21
lines changed

localization/l10n/bundle.l10n.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,6 +1515,9 @@
15151515
"Local development container": "Local development container",
15161516
"New SQL Server local development container": "New SQL Server local development container",
15171517
"New Azure SQL logical server (Preview)": "New Azure SQL logical server (Preview)",
1518+
"Build {0}": "Build {0}",
1519+
"Building {0}...": "Building {0}...",
1520+
"Build failed with exit code {0}": "Build failed with exit code {0}",
15181521
"SQL Server port number": "SQL Server port number",
15191522
"SQL Server admin password": "SQL Server admin password",
15201523
"Confirm SQL Server admin password": "Confirm SQL Server admin password",
@@ -1532,6 +1535,7 @@
15321535
"DacFx service is not available. Publish and generate script operations cannot be performed.": "DacFx service is not available. Publish and generate script operations cannot be performed.",
15331536
"DacFx service is not available. Profile loaded without deployment options. Publish and generate script operations cannot be performed.": "DacFx service is not available. Profile loaded without deployment options. Publish and generate script operations cannot be performed.",
15341537
"Failed to list databases": "Failed to list databases",
1538+
"Profile loaded but connection failed. Please connect to the server manually.": "Profile loaded but connection failed. Please connect to the server manually.",
15351539
"Schema Compare": "Schema Compare",
15361540
"Options have changed. Recompare to see the comparison?": "Options have changed. Recompare to see the comparison?",
15371541
"Failed to generate script: '{0}'/{0} is the error message returned from the generate script operation": {

localization/xliff/vscode-mssql.xlf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,15 @@
367367
<trans-unit id="++CODE++9b0636db073a79048ef4de667c447e516b7241e55ecf311c2a6f1fbe662574a1">
368368
<source xml:lang="en">Browse Fabric</source>
369369
</trans-unit>
370+
<trans-unit id="++CODE++00ea757097babdd3471ed3dbcef6f4a33b46658dfb023aa009b53fa1dd0ae30c">
371+
<source xml:lang="en">Build failed with exit code {0}</source>
372+
</trans-unit>
373+
<trans-unit id="++CODE++070df86dfd0e7f4066a971ef2e0d5b9ee8ddbb826c4edf0924026bca37ac603f">
374+
<source xml:lang="en">Build {0}</source>
375+
</trans-unit>
376+
<trans-unit id="++CODE++9168a1046e45504ed34edeecd99ec73be336161e82395399aa9a3c1d912be6b2">
377+
<source xml:lang="en">Building {0}...</source>
378+
</trans-unit>
370379
<trans-unit id="++CODE++eb6b42f54c42d2832e296319f7fdad64d46aecd1ec19b0e9c80b85b7cfc6dcae">
371380
<source xml:lang="en">CSV</source>
372381
</trans-unit>
@@ -2667,6 +2676,9 @@
26672676
<trans-unit id="++CODE++8a0d6f1276ee19a91fb06f73344d1d60f2d1f1c8d7e0454c8bb31ea00c2b9a30">
26682677
<source xml:lang="en">Profile created successfully</source>
26692678
</trans-unit>
2679+
<trans-unit id="++CODE++21eda41850777c46deb17245aa52751b4406410fdcc51f6644fc38b317201e71">
2680+
<source xml:lang="en">Profile loaded but connection failed. Please connect to the server manually.</source>
2681+
</trans-unit>
26702682
<trans-unit id="++CODE++c7b24e72fefeade2fc1706604bb5c2ee848b055ed154428128c10d5020549a7a">
26712683
<source xml:lang="en">Profile removed successfully</source>
26722684
</trans-unit>

src/constants/constants.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,13 @@ export const AzureSqlServerName = "Azure SQL server";
316316
export const DefaultSqlPortNumber = "1433";
317317
export const DefaultAdminUsername = "sa";
318318
export const DBProjectConfigurationKey = "sqlDatabaseProjects";
319+
export const sqlDatabaseProjectsExtensionId = "ms-mssql.sql-database-projects-vscode";
319320
export const enableSqlProjPreviewFeaturesKey = "enablePreviewFeatures";
320321
export const AzureSqlV12 = "AzureV12";
321322
export const PublishProfileExtension = "publish.xml";
323+
export const DacpacExtension = ".dacpac";
324+
export const dotnet = "dotnet";
325+
export const build = "build";
326+
export const sqlProjBuildTaskType = "sqlproj-build";
327+
export const msBuildProblemMatcher = "$msCompile";
328+
export const buildDirectory = "BuildDirectory";

src/constants/locConstants.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,15 @@ export class PublishProject {
13521352
public static PublishTargetNewAzureServer = l10n.t("New Azure SQL logical server (Preview)");
13531353
public static GenerateScript = l10n.t("Generate Script");
13541354
public static Publish = l10n.t("Publish");
1355+
public static BuildProjectTaskLabel(projectName: string) {
1356+
return l10n.t("Build {0}", projectName);
1357+
}
1358+
public static BuildingProjectProgress(projectName: string) {
1359+
return l10n.t("Building {0}...", projectName);
1360+
}
1361+
public static BuildFailedWithExitCode(exitCode: number) {
1362+
return l10n.t("Build failed with exit code {0}", exitCode);
1363+
}
13551364
public static SqlServerPortNumber = l10n.t("SQL Server port number");
13561365
public static SqlServerAdminPassword = l10n.t("SQL Server admin password");
13571366
public static SqlServerAdminPasswordConfirm = l10n.t("Confirm SQL Server admin password");
@@ -1383,6 +1392,9 @@ export class PublishProject {
13831392
"DacFx service is not available. Profile loaded without deployment options. Publish and generate script operations cannot be performed.",
13841393
);
13851394
public static FailedToListDatabases = l10n.t("Failed to list databases");
1395+
public static ProfileLoadedConnectionFailed = l10n.t(
1396+
"Profile loaded but connection failed. Please connect to the server manually.",
1397+
);
13861398
}
13871399

13881400
export class SchemaCompare {
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from "vscode";
7+
import * as path from "path";
8+
import * as constants from "../constants/constants";
9+
import * as Loc from "../constants/locConstants";
10+
import type * as mssql from "vscode-mssql";
11+
import { sendErrorEvent } from "../telemetry/telemetry";
12+
import { TelemetryViews, TelemetryActions } from "../sharedInterfaces/telemetry";
13+
import { getErrorMessage } from "../utils/utils";
14+
import { ProjectPropertiesResult } from "../sharedInterfaces/publishDialog";
15+
16+
/**
17+
* Controller for SQL Project operations
18+
*/
19+
export class ProjectController {
20+
/**
21+
* Builds a SQL project and returns the path to the generated DACPAC
22+
* @param projectProperties Project properties from SQL Projects service (includes projectFilePath and dacpacOutputPath)
23+
* @returns Path to the generated DACPAC file
24+
*/
25+
public async buildProject(
26+
projectProperties: ProjectPropertiesResult,
27+
): Promise<string | undefined> {
28+
try {
29+
const projectFilePath = projectProperties.projectFilePath;
30+
const projectDir = path.dirname(projectFilePath);
31+
const projectName = path.basename(projectFilePath, path.extname(projectFilePath));
32+
33+
// Construct build arguments based on project type
34+
const buildDirPath = this.getBuildDirPath();
35+
const buildArgs = this.constructBuildArguments(
36+
buildDirPath,
37+
projectProperties.projectStyle,
38+
);
39+
40+
// Create task arguments
41+
const args: string[] = [constants.build, projectFilePath, ...buildArgs];
42+
43+
// Create task definition
44+
const taskDefinition: vscode.TaskDefinition = {
45+
type: constants.sqlProjBuildTaskType,
46+
label: Loc.PublishProject.BuildProjectTaskLabel(projectName),
47+
command: constants.dotnet,
48+
args: args,
49+
problemMatcher: constants.msBuildProblemMatcher,
50+
};
51+
52+
// Create the build task
53+
const buildTask = new vscode.Task(
54+
taskDefinition,
55+
vscode.TaskScope.Workspace,
56+
taskDefinition.label,
57+
taskDefinition.type,
58+
new vscode.ShellExecution(taskDefinition.command, args, { cwd: projectDir }),
59+
taskDefinition.problemMatcher,
60+
);
61+
62+
// Execute the task and wait for completion
63+
await this.executeBuildTask(buildTask, projectName);
64+
65+
// Return the DACPAC output path
66+
return projectProperties.dacpacOutputPath;
67+
} catch (error) {
68+
// Send error telemetry
69+
sendErrorEvent(
70+
TelemetryViews.SqlProjects,
71+
TelemetryActions.BuildProject,
72+
error instanceof Error ? error : new Error(getErrorMessage(error)),
73+
false,
74+
);
75+
throw error;
76+
}
77+
}
78+
79+
/**
80+
* Gets the path to the BuildDirectory folder from the SQL Database Projects extension
81+
*/
82+
private getBuildDirPath(): string {
83+
// Use the SQL Database Projects extension's BuildDirectory which contains the required build DLLs and targets
84+
const extensionPath =
85+
vscode.extensions.getExtension(constants.sqlDatabaseProjectsExtensionId)
86+
?.extensionPath ?? "";
87+
return path.join(extensionPath, "BuildDirectory");
88+
}
89+
90+
/**
91+
* Constructs the build arguments for building a sqlproj file
92+
* @param buildDirPath Path to the SQL Tools Service directory containing build dependencies
93+
* @param projectStyle The project style (SDK-style or Legacy-style)
94+
* @returns An array of arguments to be used for building the sqlproj file
95+
*/
96+
private constructBuildArguments(
97+
buildDirPath: string,
98+
projectStyle: mssql.ProjectType,
99+
): string[] {
100+
const args: string[] = [
101+
"/p:NetCoreBuild=true",
102+
`/p:SystemDacpacsLocation="${buildDirPath}"`,
103+
];
104+
105+
// Adding NETCoreTargetsPath only for non-SDK style projects ProjectType.LegacyStyle = 1
106+
if (projectStyle === 1) {
107+
args.push(`/p:NETCoreTargetsPath="${buildDirPath}"`);
108+
}
109+
110+
return args;
111+
}
112+
113+
/**
114+
* Executes a build task and waits for it to complete
115+
* @param buildTask The VS Code task to execute
116+
* @param projectName Name of the project being built
117+
*/
118+
private async executeBuildTask(buildTask: vscode.Task, projectName: string): Promise<void> {
119+
return vscode.window.withProgress(
120+
{
121+
location: vscode.ProgressLocation.Notification,
122+
title: Loc.PublishProject.BuildingProjectProgress(projectName),
123+
cancellable: false,
124+
},
125+
async () => {
126+
// Execute the task
127+
const execution = await vscode.tasks.executeTask(buildTask);
128+
129+
// Wait for task completion
130+
return new Promise<void>((resolve, reject) => {
131+
const disposable = vscode.tasks.onDidEndTaskProcess((e) => {
132+
if (e.execution === execution) {
133+
disposable.dispose();
134+
if (e.exitCode === 0) {
135+
resolve();
136+
} else {
137+
reject(
138+
new Error(
139+
Loc.PublishProject.BuildFailedWithExitCode(
140+
e.exitCode ?? -1,
141+
),
142+
),
143+
);
144+
}
145+
}
146+
});
147+
});
148+
},
149+
);
150+
}
151+
}

src/publishProject/projectUtils.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
import * as vscode from "vscode";
77
import * as mssql from "vscode-mssql";
88
import * as constants from "../constants/constants";
9+
import * as path from "path";
910
import { SqlProjectsService } from "../services/sqlProjectsService";
1011
import { promises as fs } from "fs";
1112
import { DOMParser } from "@xmldom/xmldom";
13+
import { ProjectPropertiesResult } from "../sharedInterfaces/publishDialog";
1214

1315
/**
1416
* Checks if preview features are enabled in VS Code settings for SQL Database Projects.
@@ -104,7 +106,7 @@ export async function getProjectTargetVersion(
104106
export async function readProjectProperties(
105107
sqlProjectsService: SqlProjectsService | mssql.ISqlProjectsService,
106108
projectFilePath: string,
107-
): Promise<(mssql.GetProjectPropertiesResult & { targetVersion?: string }) | undefined> {
109+
): Promise<ProjectPropertiesResult | undefined> {
108110
try {
109111
if (!projectFilePath) {
110112
return undefined;
@@ -114,9 +116,23 @@ export async function readProjectProperties(
114116
return undefined;
115117
}
116118
const version = await getProjectTargetVersion(sqlProjectsService, projectFilePath);
119+
120+
// Calculate DACPAC output path
121+
const projectDir = path.dirname(projectFilePath);
122+
const projectName = path.basename(projectFilePath, path.extname(projectFilePath));
123+
const outputPath = path.isAbsolute(result.outputPath)
124+
? result.outputPath
125+
: path.join(projectDir, result.outputPath);
126+
const dacpacOutputPath = path.join(
127+
outputPath,
128+
`${projectName}${constants.DacpacExtension}`,
129+
);
130+
117131
return {
118132
...result,
119133
targetVersion: version,
134+
projectFilePath: projectFilePath,
135+
dacpacOutputPath: dacpacOutputPath,
120136
};
121137
} catch {
122138
return undefined;

0 commit comments

Comments
 (0)