From f88e17a2945786cb681d0295ec55396b025191b5 Mon Sep 17 00:00:00 2001 From: Leila Lali Date: Thu, 17 Dec 2020 10:27:36 -0800 Subject: [PATCH] Dacpac - Showing error message to user if operation fails (#13830) * Showing error message to user if operation fails. * Added more tests --- extensions/dacpac/src/localizedConstants.ts | 2 + .../src/test/wizardServiceInteraction.test.ts | 92 +++++++++++++++++++ .../src/wizard/dataTierApplicationWizard.ts | 48 ++++++++-- 3 files changed, 135 insertions(+), 7 deletions(-) diff --git a/extensions/dacpac/src/localizedConstants.ts b/extensions/dacpac/src/localizedConstants.ts index 78e34bd1d0..9da2c650b4 100644 --- a/extensions/dacpac/src/localizedConstants.ts +++ b/extensions/dacpac/src/localizedConstants.ts @@ -67,3 +67,5 @@ export const reservedValueErrorMessage = localize('dacfx.reservedValueErrorMessa export const trailingWhitespaceErrorMessage = localize('dacfx.trailingWhitespaceErrorMessage', "File name cannot end with a whitespace"); export const tooLongFilenameErrorMessage = localize('dacfx.tooLongFilenameErrorMessage', "File name is over 255 characters"); export function deployPlanErrorMessage(errorMessage: string): string { return localize('dacfx.deployPlanErrorMessage', "Generating deploy plan failed '{0}'", errorMessage ? errorMessage : 'Unknown'); } +export function generateDeployErrorMessage(errorMessage: string): string { return localize('dacfx.generateDeployErrorMessage', "Generating deploy script failed '{0}'", errorMessage ? errorMessage : 'Unknown'); } +export function operationErrorMessage(operation: string, errorMessage: string): string { return localize('dacfx.operationErrorMessage', "{0} operation failed '{1}'", operation, errorMessage ? errorMessage : 'Unknown'); } diff --git a/extensions/dacpac/src/test/wizardServiceInteraction.test.ts b/extensions/dacpac/src/test/wizardServiceInteraction.test.ts index 8c30bb6b23..8eb3900d00 100644 --- a/extensions/dacpac/src/test/wizardServiceInteraction.test.ts +++ b/extensions/dacpac/src/test/wizardServiceInteraction.test.ts @@ -5,8 +5,11 @@ import 'mocha'; import * as azdata from 'azdata'; +import * as vscode from 'vscode'; import * as should from 'should'; import * as sinon from 'sinon'; +import * as TypeMoq from 'typemoq'; +import * as loc from '../localizedConstants'; import { DataTierApplicationWizard, Operation } from '../wizard/dataTierApplicationWizard'; import { DacFxDataModel } from '../wizard/api/models'; import { DacFxTestService, deployOperationId, extractOperationId, importOperationId, exportOperationId, generateDeployPlan } from './testDacFxService'; @@ -60,6 +63,95 @@ describe('Dacfx wizard with connection', function (): void { await validateServiceCalls(wizard, Operation.export, exportOperationId); }); + it('executeOperation should show error message if deploy fails', async () => { + let service = TypeMoq.Mock.ofInstance(new DacFxTestService()); + service.setup(x => x.deployDacpac(TypeMoq.It.isAny(),TypeMoq.It.isAny(),TypeMoq.It.isAny(),TypeMoq.It.isAny(),TypeMoq.It.isAny(),TypeMoq.It.isAny())).returns(x => Promise.resolve({ + errorMessage: 'error1', + success: false, + operationId: '' + })); + + let wizard = new DataTierApplicationWizard(service.object); + wizard.model = {}; + wizard.model.server = connectionProfileMock; + let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves(); + wizard.selectedOperation = Operation.deploy; + await wizard.executeOperation(); + should(showErrorMessageStub.calledOnce).be.true(); + should.equal(showErrorMessageStub.getCall(0).args[0], loc.operationErrorMessage(loc.deploy, 'error1')); + }); + + it('executeOperation should show error message if export fails', async () => { + let service = TypeMoq.Mock.ofInstance(new DacFxTestService()); + service.setup(x => x.exportBacpac(TypeMoq.It.isAny(),TypeMoq.It.isAny(),TypeMoq.It.isAny(),TypeMoq.It.isAny())).returns(x => Promise.resolve({ + errorMessage: 'error1', + success: false, + operationId: '' + })); + + let wizard = new DataTierApplicationWizard(service.object); + wizard.model = {}; + wizard.model.server = connectionProfileMock; + let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves(); + wizard.selectedOperation = Operation.export; + await wizard.executeOperation(); + should(showErrorMessageStub.calledOnce).be.true(); + should.equal(showErrorMessageStub.getCall(0).args[0], loc.operationErrorMessage(loc.exportText, 'error1')); + }); + + it('executeOperation should show error message if extract fails', async () => { + let service = TypeMoq.Mock.ofInstance(new DacFxTestService()); + service.setup(x => x.extractDacpac(TypeMoq.It.isAny(),TypeMoq.It.isAny(),TypeMoq.It.isAny(),TypeMoq.It.isAny(),TypeMoq.It.isAny(),TypeMoq.It.isAny())).returns(x => Promise.resolve({ + errorMessage: 'error1', + success: false, + operationId: '' + })); + + let wizard = new DataTierApplicationWizard(service.object); + wizard.model = {}; + wizard.model.server = connectionProfileMock; + let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves(); + wizard.selectedOperation = Operation.extract; + await wizard.executeOperation(); + should(showErrorMessageStub.calledOnce).be.true(); + should.equal(showErrorMessageStub.getCall(0).args[0], loc.operationErrorMessage(loc.extract, 'error1')); + }); + + it('Should show error message if generateDeployScript fails', async () => { + let service = TypeMoq.Mock.ofInstance(new DacFxTestService()); + service.setup(x => x.generateDeployScript(TypeMoq.It.isAny(),TypeMoq.It.isAny(),TypeMoq.It.isAny(),TypeMoq.It.isAny())).returns(x => Promise.resolve({ + errorMessage: 'error1', + success: false, + operationId: '' + })); + + let wizard = new DataTierApplicationWizard(service.object); + wizard.model = {}; + wizard.model.server = connectionProfileMock; + let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves(); + await wizard.generateDeployScript(); + should(showErrorMessageStub.calledOnce).be.true(); + should.equal(showErrorMessageStub.getCall(0).args[0], loc.generateDeployErrorMessage('error1')); + }); + + it('executeOperation should show error message if import fails', async () => { + let service = TypeMoq.Mock.ofInstance(new DacFxTestService()); + service.setup(x => x.importBacpac(TypeMoq.It.isAny(),TypeMoq.It.isAny(),TypeMoq.It.isAny(),TypeMoq.It.isAny())).returns(x => Promise.resolve({ + errorMessage: 'error1', + success: false, + operationId: '' + })); + + let wizard = new DataTierApplicationWizard(service.object); + wizard.model = {}; + wizard.model.server = connectionProfileMock; + let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves(); + wizard.selectedOperation = Operation.import; + await wizard.executeOperation(); + should(showErrorMessageStub.calledOnce).be.true(); + should.equal(showErrorMessageStub.getCall(0).args[0], loc.operationErrorMessage(loc.importText, 'error1')); + }); + it('Should call deploy plan generator correctly', async () => { wizard.model.server = connectionProfileMock; diff --git a/extensions/dacpac/src/wizard/dataTierApplicationWizard.ts b/extensions/dacpac/src/wizard/dataTierApplicationWizard.ts index 84948f1df6..b36db9468c 100644 --- a/extensions/dacpac/src/wizard/dataTierApplicationWizard.ts +++ b/extensions/dacpac/src/wizard/dataTierApplicationWizard.ts @@ -242,18 +242,47 @@ export class DataTierApplicationWizard { } public async executeOperation(): Promise { + let result: mssql.DacFxResult; + switch (this.selectedOperation) { case Operation.deploy: { - return await this.deploy(); + result = await this.deploy(); + break; } case Operation.extract: { - return await this.extract(); + result = await this.extract(); + break; } case Operation.import: { - return await this.import(); + result = await this.import(); + break; } case Operation.export: { - return await this.export(); + result = await this.export(); + break; + } + } + + if (!result || !result.success) { + vscode.window.showErrorMessage(this.getOperationErrorMessage(this.selectedOperation, result?.errorMessage)); + } + + return result; + } + + private getOperationErrorMessage(operation: Operation, error: any): string { + switch (this.selectedOperation) { + case Operation.deploy: { + return loc.operationErrorMessage(loc.deploy, error); + } + case Operation.extract: { + return loc.operationErrorMessage(loc.extract, error); + } + case Operation.import: { + return loc.operationErrorMessage(loc.importText, error); + } + case Operation.export: { + return loc.operationErrorMessage(loc.exportText, error); } } } @@ -286,7 +315,7 @@ export class DataTierApplicationWizard { return await service.importBacpac(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.execute); } - private async generateDeployScript(): Promise { + public async generateDeployScript(): Promise { const service = await this.getService(msSqlProvider); const ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId); this.wizard.message = { @@ -295,7 +324,12 @@ export class DataTierApplicationWizard { description: '' }; - return await service.generateDeployScript(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.script); + let result = await service.generateDeployScript(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.script); + + if (!result || !result.success) { + vscode.window.showErrorMessage(loc.generateDeployErrorMessage(result?.errorMessage)); + } + return result; } public getPage(idx: number): Page { @@ -344,7 +378,7 @@ export class DataTierApplicationWizard { const result = await service.generateDeployPlan(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.execute); if (!result || !result.success) { - vscode.window.showErrorMessage(loc.deployPlanErrorMessage(result.errorMessage)); + vscode.window.showErrorMessage(loc.deployPlanErrorMessage(result?.errorMessage)); } return result.report;