mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-14 01:25:37 -05:00
Apply changes from remote database to sqlproj - sql-database-projects changes (#17738)
* update project from database * update project from database * Leftover merge update * Slight refactor to add vscode entrypoints * Re-adding leftover schemacompare bits that reference database project changes * Removing unnecessary function * Addiung GetDSP command to package.json * tests and a race condition fix * remove custom UUID generation code * swapping awaits for voids on promises * PR feedback * PR feedback * Hide update project command from vscode * Swapping cross-extension commands for bound extension contract * Re-adding schema compare radio buttons for sqlproj * Adding refresh after project update * Populating list of project scripts just before comparison to avoid missing script errors of project was separately edited * Adding missing await for okay button enable check * Correcting schema compare source when populated from a project * Rename UpdateDataModel to be more clear * Fix incorrectly changed type * Added new runComparison schema compare command, hooked up to sqlproj extension * Added progress indicator for "apply now" option * moved string literal to constant * Added missing await * Setting missing "saveScmpButton" state to fix test * Revert "Setting missing "saveScmpButton" state to fix test" This reverts commit 55612c9def24ac9e3398f5bbd153d21d9d3ca37f. * Removing preemptive resetWindow() call * general cleanup * PR feedback * property renames * Reverting rename; requires Tools Service change first * Adding header to updateProject * Adding missing header * PR feedback * adding missing await * Handing race condition for UI enable * Fixing broken okay enable case * Fixing enum comparison wonk Co-authored-by: Noureldine Yehia <t-nyehia@microsoft.com>
This commit is contained in:
@@ -32,6 +32,7 @@ import { AddDatabaseReferenceDialog } from '../dialogs/addDatabaseReferenceDialo
|
||||
import { ISystemDatabaseReferenceSettings, IDacpacReferenceSettings, IProjectReferenceSettings } from '../models/IDatabaseReferenceSettings';
|
||||
import { DatabaseReferenceTreeItem } from '../models/tree/databaseReferencesTreeItem';
|
||||
import { CreateProjectFromDatabaseDialog } from '../dialogs/createProjectFromDatabaseDialog';
|
||||
import { UpdateProjectFromDatabaseDialog } from '../dialogs/updateProjectFromDatabaseDialog';
|
||||
import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry';
|
||||
import { IconPathHelper } from '../common/iconHelper';
|
||||
import { DashboardData, PublishData, Status } from '../models/dashboardData/dashboardData';
|
||||
@@ -43,7 +44,8 @@ import { AutorestHelper } from '../tools/autorestHelper';
|
||||
import { createNewProjectFromDatabaseWithQuickpick } from '../dialogs/createProjectFromDatabaseQuickpick';
|
||||
import { addDatabaseReferenceQuickpick } from '../dialogs/addDatabaseReferenceQuickpick';
|
||||
import { IDeployProfile } from '../models/deploy/deployProfile';
|
||||
import { FileProjectEntry, IDatabaseReferenceProjectEntry, SqlProjectReferenceProjectEntry } from '../models/projectEntry';
|
||||
import { EntryType, FileProjectEntry, IDatabaseReferenceProjectEntry, SqlProjectReferenceProjectEntry } from '../models/projectEntry';
|
||||
import { UpdateProjectAction, UpdateProjectDataModel } from '../models/api/updateProject';
|
||||
|
||||
const maxTableLength = 10;
|
||||
|
||||
@@ -453,23 +455,28 @@ export class ProjectsController {
|
||||
return result;
|
||||
}
|
||||
|
||||
public async schemaCompare(treeNode: dataworkspace.WorkspaceTreeItem): Promise<void> {
|
||||
public async schemaCompare(source: dataworkspace.WorkspaceTreeItem | azdataType.IConnectionProfile, targetParam: any = undefined): Promise<void> {
|
||||
try {
|
||||
// check if schema compare extension is installed
|
||||
if (vscode.extensions.getExtension(constants.schemaCompareExtensionId)) {
|
||||
// build project
|
||||
const dacpacPath = await this.buildProject(treeNode);
|
||||
let sourceParam;
|
||||
|
||||
// check that dacpac exists
|
||||
if (await utils.exists(dacpacPath)) {
|
||||
TelemetryReporter.sendActionEvent(TelemetryViews.ProjectController, TelemetryActions.projectSchemaCompareCommandInvoked);
|
||||
await vscode.commands.executeCommand(constants.schemaCompareStartCommand, dacpacPath);
|
||||
if (source as dataworkspace.WorkspaceTreeItem) {
|
||||
sourceParam = this.getProjectFromContext(source as dataworkspace.WorkspaceTreeItem).projectFilePath;
|
||||
} else {
|
||||
sourceParam = source as azdataType.IConnectionProfile;
|
||||
}
|
||||
|
||||
try {
|
||||
TelemetryReporter.sendActionEvent(TelemetryViews.ProjectController, TelemetryActions.projectSchemaCompareCommandInvoked);
|
||||
await vscode.commands.executeCommand(constants.schemaCompareStartCommand, sourceParam, targetParam, undefined);
|
||||
} catch (e) {
|
||||
throw new Error(constants.buildFailedCannotStartSchemaCompare);
|
||||
}
|
||||
} else {
|
||||
throw new Error(constants.schemaCompareNotInstalled);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
const props: Record<string, string> = {};
|
||||
const message = utils.getErrorMessage(err);
|
||||
@@ -486,6 +493,67 @@ export class ProjectsController {
|
||||
}
|
||||
}
|
||||
|
||||
public async getProjectScriptFiles(projectFilePath: string): Promise<string[]> {
|
||||
const project = await Project.openProject(projectFilePath);
|
||||
|
||||
return project.files
|
||||
.filter(f => f.fsUri.fsPath.endsWith(constants.sqlFileExtension))
|
||||
.map(f => f.fsUri.fsPath);
|
||||
}
|
||||
|
||||
public async getProjectDatabaseSchemaProvider(projectFilePath: string): Promise<string> {
|
||||
const project = await Project.openProject(projectFilePath);
|
||||
return project.getProjectTargetVersion();
|
||||
}
|
||||
|
||||
public async schemaComparePublishProjectChanges(operationId: string, projectFilePath: string, folderStructure: string): Promise<mssql.SchemaComparePublishProjectResult> {
|
||||
const ext = vscode.extensions.getExtension(mssql.extension.name)!;
|
||||
const service = (await ext.activate() as mssql.IExtension).schemaCompare;
|
||||
|
||||
const projectPath = path.dirname(projectFilePath);
|
||||
|
||||
let fs: mssql.ExtractTarget;
|
||||
|
||||
switch (folderStructure) {
|
||||
case constants.file:
|
||||
fs = mssql.ExtractTarget.file;
|
||||
break;
|
||||
case constants.flat:
|
||||
fs = mssql.ExtractTarget.flat;
|
||||
break;
|
||||
case constants.objectType:
|
||||
fs = mssql.ExtractTarget.objectType;
|
||||
break;
|
||||
case constants.schema:
|
||||
fs = mssql.ExtractTarget.schema;
|
||||
break;
|
||||
case constants.schemaObjectType:
|
||||
default:
|
||||
fs = mssql.ExtractTarget.schemaObjectType;
|
||||
break;
|
||||
}
|
||||
|
||||
const result: mssql.SchemaComparePublishProjectResult = await service.schemaComparePublishProjectChanges(operationId, projectPath, fs, utils.getAzdataApi()!.TaskExecutionMode.execute);
|
||||
|
||||
const project = await Project.openProject(projectFilePath);
|
||||
|
||||
let toAdd: vscode.Uri[] = [];
|
||||
result.addedFiles.forEach((f: any) => toAdd.push(vscode.Uri.file(f)));
|
||||
await project.addToProject(toAdd);
|
||||
|
||||
let toRemove: vscode.Uri[] = [];
|
||||
result.deletedFiles.forEach((f: any) => toRemove.push(vscode.Uri.file(f)));
|
||||
|
||||
let toRemoveEntries: FileProjectEntry[] = [];
|
||||
toRemove.forEach(f => toRemoveEntries.push(new FileProjectEntry(f, f.path.replace(projectPath + '\\', ''), EntryType.File)));
|
||||
|
||||
toRemoveEntries.forEach(async f => await project.exclude(f));
|
||||
|
||||
await this.buildProject(project);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async addFolderPrompt(treeNode: dataworkspace.WorkspaceTreeItem): Promise<void> {
|
||||
const project = this.getProjectFromContext(treeNode);
|
||||
const relativePathToParent = this.getRelativePath(treeNode.element);
|
||||
@@ -778,9 +846,6 @@ export class ProjectsController {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1246,6 +1311,118 @@ export class ProjectsController {
|
||||
// TODO: Check for success; throw error
|
||||
}
|
||||
|
||||
/**
|
||||
* Display dialog for user to configure existing SQL Project with the changes/differences from a database
|
||||
*/
|
||||
public async updateProjectFromDatabase(context: azdataType.IConnectionProfile | mssqlVscode.ITreeNodeInfo | dataworkspace.WorkspaceTreeItem): Promise<UpdateProjectFromDatabaseDialog> {
|
||||
let connection: azdataType.IConnectionProfile | mssqlVscode.IConnectionInfo | undefined;
|
||||
let project: Project | undefined;
|
||||
|
||||
try {
|
||||
if ('connectionProfile' in context) {
|
||||
connection = this.getConnectionProfileFromContext(context as azdataType.IConnectionProfile | mssqlVscode.ITreeNodeInfo);
|
||||
}
|
||||
} catch { }
|
||||
|
||||
try {
|
||||
if ('treeDataProvider' in context) {
|
||||
project = this.getProjectFromContext(context as dataworkspace.WorkspaceTreeItem);
|
||||
}
|
||||
} catch { }
|
||||
|
||||
const updateProjectFromDatabaseDialog = this.getUpdateProjectFromDatabaseDialog(connection, project);
|
||||
|
||||
updateProjectFromDatabaseDialog.updateProjectFromDatabaseCallback = async (model) => await this.updateProjectFromDatabaseCallback(model);
|
||||
|
||||
await updateProjectFromDatabaseDialog.openDialog();
|
||||
|
||||
return updateProjectFromDatabaseDialog;
|
||||
}
|
||||
|
||||
public getUpdateProjectFromDatabaseDialog(connection: azdataType.IConnectionProfile | mssqlVscode.IConnectionInfo | undefined, project: Project | undefined): UpdateProjectFromDatabaseDialog {
|
||||
return new UpdateProjectFromDatabaseDialog(connection, project);
|
||||
}
|
||||
|
||||
public async updateProjectFromDatabaseCallback(model: UpdateProjectDataModel) {
|
||||
try {
|
||||
await this.updateProjectFromDatabaseApiCall(model);
|
||||
} catch (err) {
|
||||
void vscode.window.showErrorMessage(utils.getErrorMessage(err));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the DacFx service to update an existing SQL Project with the changes/differences from a database
|
||||
*/
|
||||
public async updateProjectFromDatabaseApiCall(model: UpdateProjectDataModel): Promise<void> {
|
||||
if (model.action === UpdateProjectAction.Compare) {
|
||||
await vscode.commands.executeCommand(constants.schemaCompareRunComparisonCommand, model.sourceEndpointInfo, model.targetEndpointInfo, true, undefined);
|
||||
} else if (model.action === UpdateProjectAction.Update) {
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: constants.updatingProjectFromDatabase(path.basename(model.targetEndpointInfo.projectFilePath), model.sourceEndpointInfo.databaseName),
|
||||
cancellable: false
|
||||
}, async (_progress, _token) => {
|
||||
return this.schemaCompareAndUpdateProject(model.sourceEndpointInfo, model.targetEndpointInfo);
|
||||
});
|
||||
|
||||
void vscode.commands.executeCommand(constants.refreshDataWorkspaceCommand);
|
||||
utils.getDataWorkspaceExtensionApi().showProjectsView();
|
||||
} else {
|
||||
throw new Error(`Unknown UpdateProjectAction: ${model.action}`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private async schemaCompareAndUpdateProject(source: mssql.SchemaCompareEndpointInfo, target: mssql.SchemaCompareEndpointInfo): Promise<void> {
|
||||
// Run schema comparison
|
||||
const ext = vscode.extensions.getExtension(mssql.extension.name)!;
|
||||
const service = (await ext.activate() as mssql.IExtension).schemaCompare;
|
||||
const deploymentOptions = await service.schemaCompareGetDefaultOptions();
|
||||
const operationId = UUID.generateUuid();
|
||||
|
||||
target.targetScripts = await this.getProjectScriptFiles(target.projectFilePath);
|
||||
target.dataSchemaProvider = await this.getProjectDatabaseSchemaProvider(target.projectFilePath);
|
||||
|
||||
TelemetryReporter.sendActionEvent(TelemetryViews.ProjectController, 'SchemaComparisonStarted');
|
||||
|
||||
// Perform schema comparison. Results are cached in SqlToolsService under the operationId
|
||||
const comparisonResult: mssql.SchemaCompareResult = await service.schemaCompare(
|
||||
operationId, source, target, utils.getAzdataApi()!.TaskExecutionMode.execute, deploymentOptions.defaultDeploymentOptions
|
||||
);
|
||||
|
||||
if (!comparisonResult || !comparisonResult.success) {
|
||||
TelemetryReporter.createErrorEvent(TelemetryViews.ProjectController, 'SchemaComparisonFailed')
|
||||
.withAdditionalProperties({
|
||||
operationId: comparisonResult.operationId
|
||||
}).send();
|
||||
await vscode.window.showErrorMessage(constants.compareErrorMessage(comparisonResult?.errorMessage));
|
||||
return;
|
||||
}
|
||||
|
||||
TelemetryReporter.createActionEvent(TelemetryViews.ProjectController, 'SchemaComparisonFinished')
|
||||
.withAdditionalProperties({
|
||||
'endTime': Date.now().toString(),
|
||||
'operationId': comparisonResult.operationId
|
||||
}).send();
|
||||
|
||||
if (comparisonResult.areEqual) {
|
||||
void vscode.window.showInformationMessage(constants.equalComparison);
|
||||
return;
|
||||
}
|
||||
|
||||
// Publish the changes (retrieved from the cache by operationId)
|
||||
const publishResult = await this.schemaComparePublishProjectChanges(operationId, target.projectFilePath, target.folderStructure);
|
||||
|
||||
if (publishResult.success) {
|
||||
void vscode.window.showInformationMessage(constants.applySuccess);
|
||||
} else {
|
||||
void vscode.window.showErrorMessage(constants.applyError(publishResult.errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a flat list of all files and folder under a folder.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user