diff --git a/extensions/sql-database-projects/src/common/promise.ts b/extensions/sql-database-projects/src/common/promise.ts index 241353c31c..c8d40d7a67 100644 --- a/extensions/sql-database-projects/src/common/promise.ts +++ b/extensions/sql-database-projects/src/common/promise.ts @@ -6,7 +6,20 @@ /** * Deferred promise */ -export interface Deferred { - resolve: (result: T | Promise) => void; - reject: (reason: any) => void; +export class Deferred { + promise: Promise; + resolve!: (value: T | PromiseLike) => void; + reject!: (reason?: any) => void; + constructor() { + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + } + + then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Thenable; + then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => void): Thenable; + then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Thenable { + return this.promise.then(onfulfilled, onrejected); + } } diff --git a/extensions/sql-database-projects/src/controllers/mainController.ts b/extensions/sql-database-projects/src/controllers/mainController.ts index acce729944..e8aa915db2 100644 --- a/extensions/sql-database-projects/src/controllers/mainController.ts +++ b/extensions/sql-database-projects/src/controllers/mainController.ts @@ -54,7 +54,7 @@ export default class MainController implements vscode.Disposable { vscode.commands.registerCommand('sqlDatabaseProjects.properties', async (node: WorkspaceTreeItem) => { return vscode.window.showErrorMessage(`Properties not yet implemented: ${node.element.uri.path}`); }); // TODO vscode.commands.registerCommand('sqlDatabaseProjects.build', async (node: WorkspaceTreeItem) => { return this.projectsController.buildProject(node); }); - vscode.commands.registerCommand('sqlDatabaseProjects.publish', async (node: WorkspaceTreeItem) => { this.projectsController.publishProject(node); }); + vscode.commands.registerCommand('sqlDatabaseProjects.publish', async (node: WorkspaceTreeItem) => { return this.projectsController.publishProject(node); }); vscode.commands.registerCommand('sqlDatabaseProjects.schemaCompare', async (node: WorkspaceTreeItem) => { return this.projectsController.schemaCompare(node); }); vscode.commands.registerCommand('sqlDatabaseProjects.createProjectFromDatabase', async (context: azdataType.IConnectionProfile | vscodeMssql.ITreeNodeInfo | undefined) => { return this.projectsController.createProjectFromDatabase(context); }); vscode.commands.registerCommand('sqlDatabaseProjects.generateProjectFromOpenApiSpec', async () => { return this.projectsController.generateProjectFromOpenApiSpec(); }); diff --git a/extensions/sql-database-projects/src/controllers/projectController.ts b/extensions/sql-database-projects/src/controllers/projectController.ts index e23eacd5ae..c9c018a297 100644 --- a/extensions/sql-database-projects/src/controllers/projectController.ts +++ b/extensions/sql-database-projects/src/controllers/projectController.ts @@ -300,13 +300,13 @@ export class ProjectsController { * Builds and publishes a project * @param treeNode a treeItem in a project's hierarchy, to be used to obtain a Project */ - public publishProject(treeNode: dataworkspace.WorkspaceTreeItem): PublishDatabaseDialog; + public async publishProject(treeNode: dataworkspace.WorkspaceTreeItem): Promise; /** * Builds and publishes a project * @param project Project to be built and published */ - public publishProject(project: Project): PublishDatabaseDialog; - public publishProject(context: Project | dataworkspace.WorkspaceTreeItem): PublishDatabaseDialog | undefined { + public async publishProject(project: Project): Promise; + public async publishProject(context: Project | dataworkspace.WorkspaceTreeItem): Promise { const project: Project = this.getProjectFromContext(context); if (utils.getAzdataApi()) { let publishDatabaseDialog = this.getPublishDialog(project); @@ -317,10 +317,9 @@ export class ProjectsController { publishDatabaseDialog.openDialog(); - return publishDatabaseDialog; + return publishDatabaseDialog.waitForClose(); } else { - void launchPublishDatabaseQuickpick(project, this); - return undefined; + return launchPublishDatabaseQuickpick(project, this); } } diff --git a/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceDialog.ts b/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceDialog.ts index d2290db91b..4e8896b37c 100644 --- a/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceDialog.ts +++ b/extensions/sql-database-projects/src/dialogs/addDatabaseReferenceDialog.ts @@ -46,8 +46,7 @@ export class AddDatabaseReferenceDialog { public currentReferenceType: ReferenceType | undefined; private toDispose: vscode.Disposable[] = []; - private initDialogComplete: Deferred | undefined; - private initDialogPromise: Promise = new Promise((resolve, reject) => this.initDialogComplete = { resolve, reject }); + private initDialogComplete: Deferred = new Deferred(); public addReference: ((proj: Project, settings: ISystemDatabaseReferenceSettings | IDacpacReferenceSettings | IProjectReferenceSettings) => any) | undefined; @@ -86,7 +85,7 @@ export class AddDatabaseReferenceDialog { this.dialog.cancelButton.label = constants.cancelButtonText; utils.getAzdataApi()!.window.openDialog(this.dialog); - await this.initDialogPromise; + await this.initDialogComplete.promise; } private dispose(): void { @@ -144,7 +143,7 @@ export class AddDatabaseReferenceDialog { await this.systemDatabaseRadioButton?.focus(); } - this.initDialogComplete?.resolve(); + this.initDialogComplete.resolve(); }); } diff --git a/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseDialog.ts b/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseDialog.ts index 03ed202dfe..6f4e75aa5f 100644 --- a/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseDialog.ts +++ b/extensions/sql-database-projects/src/dialogs/createProjectFromDatabaseDialog.ts @@ -29,8 +29,7 @@ export class CreateProjectFromDatabaseDialog { private formBuilder: azdataType.FormBuilder | undefined; private connectionId: string | undefined; private toDispose: vscode.Disposable[] = []; - private initDialogComplete!: Deferred; - private initDialogPromise: Promise = new Promise((resolve, reject) => this.initDialogComplete = { resolve, reject }); + private initDialogComplete: Deferred = new Deferred(); public createProjectFromDatabaseCallback: ((model: ImportDataModel) => any) | undefined; @@ -51,7 +50,7 @@ export class CreateProjectFromDatabaseDialog { this.dialog.cancelButton.label = constants.cancelButtonText; getAzdataApi()!.window.openDialog(this.dialog); - await this.initDialogPromise; + await this.initDialogComplete.promise; if (this.profile) { await this.updateConnectionComponents(getConnectionName(this.profile), this.profile.id, this.profile.databaseName!); diff --git a/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts b/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts index 246e280674..322c982ca8 100644 --- a/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts +++ b/extensions/sql-database-projects/src/dialogs/publishDatabaseDialog.ts @@ -16,6 +16,7 @@ import { IconPathHelper } from '../common/iconHelper'; import { cssStyles } from '../common/uiConstants'; import { getConnectionName } from './utils'; import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/telemetry'; +import { Deferred } from '../common/promise'; interface DataSourceDropdownValue extends azdataType.CategoryValue { dataSource: SqlConnectionDataSource; @@ -44,6 +45,8 @@ export class PublishDatabaseDialog { private profileUsed: boolean = false; private serverName: string | undefined; + private completionPromise: Deferred = new Deferred(); + private toDispose: vscode.Disposable[] = []; public publish: ((proj: Project, profile: IDeploySettings) => any) | undefined; @@ -71,6 +74,13 @@ export class PublishDatabaseDialog { this.dialog.customButtons.push(generateScriptButton); utils.getAzdataApi()!.window.openDialog(this.dialog); + // Complete the promise when we're done - we use a disposable here instead of a CloseValidator because we have custom buttons (generate script) + // which don't go through that logic. + this.toDispose.push({ dispose: () => { this.completionPromise.resolve(); } }); + } + + public waitForClose(): Promise { + return this.completionPromise.promise; } private dispose(): void { diff --git a/extensions/sql-database-projects/src/test/projectController.test.ts b/extensions/sql-database-projects/src/test/projectController.test.ts index 9c4fc845dc..d9ffd973f4 100644 --- a/extensions/sql-database-projects/src/test/projectController.test.ts +++ b/extensions/sql-database-projects/src/test/projectController.test.ts @@ -364,7 +364,7 @@ describe('ProjectsController', function (): void { projController.callBase = true; projController.setup(x => x.getPublishDialog(TypeMoq.It.isAny())).returns(() => publishDialog.object); - projController.object.publishProject(new Project('FakePath')); + void projController.object.publishProject(new Project('FakePath')); should(opened).equal(true); }); @@ -395,13 +395,15 @@ describe('ProjectsController', function (): void { return Promise.resolve(undefined); }); - let dialog = projController.object.publishProject(proj); - await dialog.publishClick(); + let dialogPromise = projController.object.publishProject(proj); + await publishDialog.object.publishClick(); + await dialogPromise; should(holler).equal(publishHoller, 'executionCallback() is supposed to have been setup and called for Publish scenario'); - dialog = projController.object.publishProject(proj); - await dialog.generateScriptClick(); + dialogPromise = projController.object.publishProject(proj); + await publishDialog.object.generateScriptClick(); + await dialogPromise; should(holler).equal(generateHoller, 'executionCallback() is supposed to have been setup and called for GenerateScript scenario'); });