mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Adding Dacpac extension telemetry and core wizard/page telemetry updates(#13859)
* Dacpac telmetry code changes * Removed added spaces * Generate deployScript accessibility changed back to public * code review suggessions updates * dacpac extension tests fixes * Updated time and filesize methods allowing general return values * Telemetry code updates * Dacpac Telemetry potential data loss capture and PII error excluded * Dacpac telemetry code updates for comments * Wizard pages navigation telemetry event capture moved to the core * DacpacTelemetry code updates * Extension wizard cancel telemetry for data loss * Dacpac telemetry pagename and small code updates * final Dacpac telemetry code updates...
This commit is contained in:
committed by
GitHub
parent
07d798c949
commit
0316d9ac57
@@ -14,5 +14,10 @@ let packageInfo = Utils.getPackageInfo(packageJson);
|
|||||||
export const TelemetryReporter = new AdsTelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
|
export const TelemetryReporter = new AdsTelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
|
||||||
|
|
||||||
export enum TelemetryViews {
|
export enum TelemetryViews {
|
||||||
SelectOperationPage = 'SelectOperationPage'
|
DataTierApplicationWizard = 'DataTierApplicationWizard',
|
||||||
|
DeployDacpac = 'DeployDacpac',
|
||||||
|
DeployPlanPage = 'DeployPlanPage',
|
||||||
|
ExportBacpac = 'ExportBacpac',
|
||||||
|
ExtractDacpac = 'ExtractDacpac',
|
||||||
|
ImportBacpac = 'ImportBacpac'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import * as should from 'should';
|
|||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
import * as loc from '../localizedConstants';
|
import * as loc from '../localizedConstants';
|
||||||
|
import * as utils from '../utils';
|
||||||
import { DataTierApplicationWizard, Operation } from '../wizard/dataTierApplicationWizard';
|
import { DataTierApplicationWizard, Operation } from '../wizard/dataTierApplicationWizard';
|
||||||
import { DacFxDataModel } from '../wizard/api/models';
|
import { DacFxDataModel } from '../wizard/api/models';
|
||||||
import { DacFxTestService, deployOperationId, extractOperationId, importOperationId, exportOperationId, generateDeployPlan } from './testDacFxService';
|
import { DacFxTestService, deployOperationId, extractOperationId, importOperationId, exportOperationId, generateDeployPlan } from './testDacFxService';
|
||||||
@@ -56,6 +57,11 @@ describe('Dacfx wizard with connection', function (): void {
|
|||||||
|
|
||||||
it('Should call all service methods correctly', async () => {
|
it('Should call all service methods correctly', async () => {
|
||||||
wizard.model.server = connectionProfileMock;
|
wizard.model.server = connectionProfileMock;
|
||||||
|
wizard.model.potentialDataLoss = true;
|
||||||
|
wizard.model.upgradeExisting = true;
|
||||||
|
|
||||||
|
const fileSizeStub = sinon.stub(utils, 'tryGetFileSize');
|
||||||
|
fileSizeStub.resolves(TypeMoq.It.isAnyNumber());
|
||||||
|
|
||||||
await validateServiceCalls(wizard, Operation.deploy, deployOperationId);
|
await validateServiceCalls(wizard, Operation.deploy, deployOperationId);
|
||||||
await validateServiceCalls(wizard, Operation.extract, extractOperationId);
|
await validateServiceCalls(wizard, Operation.extract, extractOperationId);
|
||||||
@@ -65,7 +71,7 @@ describe('Dacfx wizard with connection', function (): void {
|
|||||||
|
|
||||||
it('executeOperation should show error message if deploy fails', async () => {
|
it('executeOperation should show error message if deploy fails', async () => {
|
||||||
let service = TypeMoq.Mock.ofInstance(new DacFxTestService());
|
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({
|
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',
|
errorMessage: 'error1',
|
||||||
success: false,
|
success: false,
|
||||||
operationId: ''
|
operationId: ''
|
||||||
@@ -74,6 +80,10 @@ describe('Dacfx wizard with connection', function (): void {
|
|||||||
let wizard = new DataTierApplicationWizard(service.object);
|
let wizard = new DataTierApplicationWizard(service.object);
|
||||||
wizard.model = <DacFxDataModel>{};
|
wizard.model = <DacFxDataModel>{};
|
||||||
wizard.model.server = connectionProfileMock;
|
wizard.model.server = connectionProfileMock;
|
||||||
|
wizard.model.potentialDataLoss = true;
|
||||||
|
wizard.model.upgradeExisting = true;
|
||||||
|
const fileSizeStub = sinon.stub(utils, 'tryGetFileSize');
|
||||||
|
fileSizeStub.resolves(TypeMoq.It.isAnyNumber());
|
||||||
let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves();
|
let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves();
|
||||||
wizard.selectedOperation = Operation.deploy;
|
wizard.selectedOperation = Operation.deploy;
|
||||||
await wizard.executeOperation();
|
await wizard.executeOperation();
|
||||||
@@ -83,7 +93,7 @@ describe('Dacfx wizard with connection', function (): void {
|
|||||||
|
|
||||||
it('executeOperation should show error message if export fails', async () => {
|
it('executeOperation should show error message if export fails', async () => {
|
||||||
let service = TypeMoq.Mock.ofInstance(new DacFxTestService());
|
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({
|
service.setup(x => x.exportBacpac(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(x => Promise.resolve({
|
||||||
errorMessage: 'error1',
|
errorMessage: 'error1',
|
||||||
success: false,
|
success: false,
|
||||||
operationId: ''
|
operationId: ''
|
||||||
@@ -92,6 +102,8 @@ describe('Dacfx wizard with connection', function (): void {
|
|||||||
let wizard = new DataTierApplicationWizard(service.object);
|
let wizard = new DataTierApplicationWizard(service.object);
|
||||||
wizard.model = <DacFxDataModel>{};
|
wizard.model = <DacFxDataModel>{};
|
||||||
wizard.model.server = connectionProfileMock;
|
wizard.model.server = connectionProfileMock;
|
||||||
|
const fileSizeStub = sinon.stub(utils, 'tryGetFileSize');
|
||||||
|
fileSizeStub.resolves(TypeMoq.It.isAnyNumber());
|
||||||
let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves();
|
let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves();
|
||||||
wizard.selectedOperation = Operation.export;
|
wizard.selectedOperation = Operation.export;
|
||||||
await wizard.executeOperation();
|
await wizard.executeOperation();
|
||||||
@@ -101,7 +113,7 @@ describe('Dacfx wizard with connection', function (): void {
|
|||||||
|
|
||||||
it('executeOperation should show error message if extract fails', async () => {
|
it('executeOperation should show error message if extract fails', async () => {
|
||||||
let service = TypeMoq.Mock.ofInstance(new DacFxTestService());
|
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({
|
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',
|
errorMessage: 'error1',
|
||||||
success: false,
|
success: false,
|
||||||
operationId: ''
|
operationId: ''
|
||||||
@@ -110,6 +122,8 @@ describe('Dacfx wizard with connection', function (): void {
|
|||||||
let wizard = new DataTierApplicationWizard(service.object);
|
let wizard = new DataTierApplicationWizard(service.object);
|
||||||
wizard.model = <DacFxDataModel>{};
|
wizard.model = <DacFxDataModel>{};
|
||||||
wizard.model.server = connectionProfileMock;
|
wizard.model.server = connectionProfileMock;
|
||||||
|
const fileSizeStub = sinon.stub(utils, 'tryGetFileSize');
|
||||||
|
fileSizeStub.resolves(TypeMoq.It.isAnyNumber());
|
||||||
let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves();
|
let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves();
|
||||||
wizard.selectedOperation = Operation.extract;
|
wizard.selectedOperation = Operation.extract;
|
||||||
await wizard.executeOperation();
|
await wizard.executeOperation();
|
||||||
@@ -119,7 +133,7 @@ describe('Dacfx wizard with connection', function (): void {
|
|||||||
|
|
||||||
it('Should show error message if generateDeployScript fails', async () => {
|
it('Should show error message if generateDeployScript fails', async () => {
|
||||||
let service = TypeMoq.Mock.ofInstance(new DacFxTestService());
|
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({
|
service.setup(x => x.generateDeployScript(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(x => Promise.resolve({
|
||||||
errorMessage: 'error1',
|
errorMessage: 'error1',
|
||||||
success: false,
|
success: false,
|
||||||
operationId: ''
|
operationId: ''
|
||||||
@@ -128,6 +142,9 @@ describe('Dacfx wizard with connection', function (): void {
|
|||||||
let wizard = new DataTierApplicationWizard(service.object);
|
let wizard = new DataTierApplicationWizard(service.object);
|
||||||
wizard.model = <DacFxDataModel>{};
|
wizard.model = <DacFxDataModel>{};
|
||||||
wizard.model.server = connectionProfileMock;
|
wizard.model.server = connectionProfileMock;
|
||||||
|
wizard.model.potentialDataLoss = true;
|
||||||
|
const fileSizeStub = sinon.stub(utils, 'tryGetFileSize');
|
||||||
|
fileSizeStub.resolves(TypeMoq.It.isAnyNumber());
|
||||||
let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves();
|
let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves();
|
||||||
await wizard.generateDeployScript();
|
await wizard.generateDeployScript();
|
||||||
should(showErrorMessageStub.calledOnce).be.true();
|
should(showErrorMessageStub.calledOnce).be.true();
|
||||||
@@ -136,7 +153,7 @@ describe('Dacfx wizard with connection', function (): void {
|
|||||||
|
|
||||||
it('executeOperation should show error message if import fails', async () => {
|
it('executeOperation should show error message if import fails', async () => {
|
||||||
let service = TypeMoq.Mock.ofInstance(new DacFxTestService());
|
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({
|
service.setup(x => x.importBacpac(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(x => Promise.resolve({
|
||||||
errorMessage: 'error1',
|
errorMessage: 'error1',
|
||||||
success: false,
|
success: false,
|
||||||
operationId: ''
|
operationId: ''
|
||||||
@@ -145,6 +162,8 @@ describe('Dacfx wizard with connection', function (): void {
|
|||||||
let wizard = new DataTierApplicationWizard(service.object);
|
let wizard = new DataTierApplicationWizard(service.object);
|
||||||
wizard.model = <DacFxDataModel>{};
|
wizard.model = <DacFxDataModel>{};
|
||||||
wizard.model.server = connectionProfileMock;
|
wizard.model.server = connectionProfileMock;
|
||||||
|
const fileSizeStub = sinon.stub(utils, 'tryGetFileSize');
|
||||||
|
fileSizeStub.resolves(TypeMoq.It.isAnyNumber());
|
||||||
let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves();
|
let showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').resolves();
|
||||||
wizard.selectedOperation = Operation.import;
|
wizard.selectedOperation = Operation.import;
|
||||||
await wizard.executeOperation();
|
await wizard.executeOperation();
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
export interface IPackageInfo {
|
export interface IPackageInfo {
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
@@ -22,14 +24,16 @@ export function getPackageInfo(packageJson: any): IPackageInfo | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map an error message into a short name for the type of error.
|
* Get file size from the file stats using the file path uri
|
||||||
* @param msg The error message to map
|
* If the file does not exists, purposely returning undefined instead of throwing an error for telemetry purpose.
|
||||||
|
* @param uri The file path
|
||||||
*/
|
*/
|
||||||
export function getTelemetryErrorType(msg: string): string {
|
export async function tryGetFileSize(uri: string): Promise<number | undefined> {
|
||||||
if (msg && msg.indexOf('Object reference not set to an instance of an object') !== -1) {
|
try {
|
||||||
return 'ObjectReferenceNotSet';
|
const stats = await fs.promises.stat(uri);
|
||||||
|
return stats?.size;
|
||||||
}
|
}
|
||||||
else {
|
catch (e) {
|
||||||
return 'Other';
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,4 +16,5 @@ export interface DacFxDataModel {
|
|||||||
filePath: string;
|
filePath: string;
|
||||||
version: string;
|
version: string;
|
||||||
upgradeExisting: boolean;
|
upgradeExisting: boolean;
|
||||||
|
potentialDataLoss: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as loc from '../localizedConstants';
|
import * as loc from '../localizedConstants';
|
||||||
|
import * as mssql from '../../../mssql';
|
||||||
|
import * as utils from '../utils';
|
||||||
import { SelectOperationPage } from './pages/selectOperationpage';
|
import { SelectOperationPage } from './pages/selectOperationpage';
|
||||||
import { DeployConfigPage } from './pages/deployConfigPage';
|
import { DeployConfigPage } from './pages/deployConfigPage';
|
||||||
import { DeployPlanPage } from './pages/deployPlanPage';
|
import { DeployPlanPage } from './pages/deployPlanPage';
|
||||||
@@ -15,7 +17,8 @@ import { ExtractConfigPage } from './pages/extractConfigPage';
|
|||||||
import { ImportConfigPage } from './pages/importConfigPage';
|
import { ImportConfigPage } from './pages/importConfigPage';
|
||||||
import { DacFxDataModel } from './api/models';
|
import { DacFxDataModel } from './api/models';
|
||||||
import { BasePage } from './api/basePage';
|
import { BasePage } from './api/basePage';
|
||||||
import * as mssql from '../../../mssql';
|
import { TelemetryReporter, TelemetryViews } from '../telemetry';
|
||||||
|
import { TelemetryEventMeasures, TelemetryEventProperties } from 'ads-extension-telemetry';
|
||||||
|
|
||||||
const msSqlProvider = 'MSSQL';
|
const msSqlProvider = 'MSSQL';
|
||||||
class Page {
|
class Page {
|
||||||
@@ -84,7 +87,7 @@ export class DataTierApplicationWizard {
|
|||||||
public selectedOperation: Operation;
|
public selectedOperation: Operation;
|
||||||
|
|
||||||
constructor(dacfxInputService?: mssql.IDacFxService) {
|
constructor(dacfxInputService?: mssql.IDacFxService) {
|
||||||
this.wizard = azdata.window.createWizard(loc.wizardTitle);
|
this.wizard = azdata.window.createWizard(loc.wizardTitle, 'Data Tier Application Wizard');
|
||||||
this.dacfxService = dacfxInputService;
|
this.dacfxService = dacfxInputService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +113,8 @@ export class DataTierApplicationWizard {
|
|||||||
}
|
}
|
||||||
// don't open the wizard if connection dialog is cancelled
|
// don't open the wizard if connection dialog is cancelled
|
||||||
if (!this.connection) {
|
if (!this.connection) {
|
||||||
|
//Reporting Dacpac wizard cancelled event to Telemetry
|
||||||
|
TelemetryReporter.sendActionEvent(TelemetryViews.DataTierApplicationWizard, 'ConnectionDialogCancelled');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,13 +128,13 @@ export class DataTierApplicationWizard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setPages(): void {
|
public setPages(): void {
|
||||||
let selectOperationWizardPage = azdata.window.createWizardPage(loc.selectOperationPageName);
|
let selectOperationWizardPage = azdata.window.createWizardPage(loc.selectOperationPageName, 'Select an Operation Page');
|
||||||
let deployConfigWizardPage = azdata.window.createWizardPage(loc.deployConfigPageName);
|
let deployConfigWizardPage = azdata.window.createWizardPage(loc.deployConfigPageName, 'Deploy Config Page');
|
||||||
let deployPlanWizardPage = azdata.window.createWizardPage(loc.deployPlanPageName);
|
let deployPlanWizardPage = azdata.window.createWizardPage(loc.deployPlanPageName, 'Deploy Plan Page');
|
||||||
let summaryWizardPage = azdata.window.createWizardPage(loc.summaryPageName);
|
let summaryWizardPage = azdata.window.createWizardPage(loc.summaryPageName, 'Summary Page');
|
||||||
let extractConfigWizardPage = azdata.window.createWizardPage(loc.extractConfigPageName);
|
let extractConfigWizardPage = azdata.window.createWizardPage(loc.extractConfigPageName, 'Extract Config Page');
|
||||||
let importConfigWizardPage = azdata.window.createWizardPage(loc.importConfigPageName);
|
let importConfigWizardPage = azdata.window.createWizardPage(loc.importConfigPageName, 'Import Config Page');
|
||||||
let exportConfigWizardPage = azdata.window.createWizardPage(loc.exportConfigPageName);
|
let exportConfigWizardPage = azdata.window.createWizardPage(loc.exportConfigPageName, 'Export Config Page');
|
||||||
|
|
||||||
this.pages.set(PageName.selectOperation, new Page(selectOperationWizardPage));
|
this.pages.set(PageName.selectOperation, new Page(selectOperationWizardPage));
|
||||||
this.pages.set(PageName.deployConfig, new Page(deployConfigWizardPage));
|
this.pages.set(PageName.deployConfig, new Page(deployConfigWizardPage));
|
||||||
@@ -206,6 +211,7 @@ export class DataTierApplicationWizard {
|
|||||||
this.wizard.generateScriptButton.hidden = true;
|
this.wizard.generateScriptButton.hidden = true;
|
||||||
this.wizard.generateScriptButton.onClick(async () => await this.generateDeployScript());
|
this.wizard.generateScriptButton.onClick(async () => await this.generateDeployScript());
|
||||||
this.wizard.doneButton.onClick(async () => await this.executeOperation());
|
this.wizard.doneButton.onClick(async () => await this.executeOperation());
|
||||||
|
this.wizard.cancelButton.onClick(() => this.cancelDataTierApplicationWizard());
|
||||||
}
|
}
|
||||||
|
|
||||||
public registerNavigationValidator(validator: (pageChangeInfo: azdata.window.WizardPageChangeInfo) => boolean) {
|
public registerNavigationValidator(validator: (pageChangeInfo: azdata.window.WizardPageChangeInfo) => boolean) {
|
||||||
@@ -287,48 +293,161 @@ export class DataTierApplicationWizard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deploy(): Promise<mssql.DacFxResult> {
|
// Cancel button on click event is using to send the data loss information to telemetry
|
||||||
const service = await this.getService(msSqlProvider);
|
private cancelDataTierApplicationWizard(): void {
|
||||||
const ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
TelemetryReporter.createActionEvent(TelemetryViews.DataTierApplicationWizard, 'WizardCanceled')
|
||||||
|
.withAdditionalProperties({
|
||||||
|
isPotentialDataLoss: this.model.potentialDataLoss.toString()
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
return await service.deployDacpac(this.model.filePath, this.model.database, this.model.upgradeExisting, ownerUri, azdata.TaskExecutionMode.execute);
|
public async deploy(): Promise<mssql.DacFxResult> {
|
||||||
|
const deployStartTime = new Date().getTime();
|
||||||
|
let service: mssql.IDacFxService;
|
||||||
|
let ownerUri: string;
|
||||||
|
let result: mssql.DacFxResult;
|
||||||
|
let additionalProps: TelemetryEventProperties = {};
|
||||||
|
let additionalMeasurements: TelemetryEventMeasures = {};
|
||||||
|
try {
|
||||||
|
service = await this.getService(msSqlProvider);
|
||||||
|
ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
||||||
|
result = await service.deployDacpac(this.model.filePath, this.model.database, this.model.upgradeExisting, ownerUri, azdata.TaskExecutionMode.execute);
|
||||||
|
} catch (e) {
|
||||||
|
additionalProps.exceptionOccurred = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
// If result is null which means exception occured, will be adding additional props to the Telemetry
|
||||||
|
if (!result) {
|
||||||
|
additionalProps = { ...additionalProps, ...this.getDacServiceArgsAsProps(service, this.model.database, this.model.filePath, ownerUri) };
|
||||||
|
}
|
||||||
|
additionalProps.deploymentStatus = result?.success.toString();
|
||||||
|
additionalProps.upgradeExistingDatabase = this.model.upgradeExisting.toString();
|
||||||
|
additionalProps.potentialDataLoss = this.model.potentialDataLoss.toString();
|
||||||
|
|
||||||
|
additionalMeasurements.deployDacpacFileSizeBytes = await utils.tryGetFileSize(this.model.filePath);
|
||||||
|
additionalMeasurements.totalDurationMs = (new Date().getTime() - deployStartTime);
|
||||||
|
|
||||||
|
// Deploy Dacpac: 'Deploy button' clicked in deploy summary page, Reporting the event selection to the telemetry
|
||||||
|
this.sendDacServiceTelemetryEvent(TelemetryViews.DeployDacpac, 'DeployDacpacOperation', additionalProps, additionalMeasurements);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async extract(): Promise<mssql.DacFxResult> {
|
private async extract(): Promise<mssql.DacFxResult> {
|
||||||
const service = await this.getService(msSqlProvider);
|
const extractStartTime = new Date().getTime();
|
||||||
const ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
let service: mssql.IDacFxService;
|
||||||
|
let ownerUri: string;
|
||||||
|
let result: mssql.DacFxResult;
|
||||||
|
let additionalProps: TelemetryEventProperties = {};
|
||||||
|
let additionalMeasurements: TelemetryEventMeasures = {};
|
||||||
|
try {
|
||||||
|
service = await this.getService(msSqlProvider);
|
||||||
|
ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
||||||
|
result = await service.extractDacpac(this.model.database, this.model.filePath, this.model.database, this.model.version, ownerUri, azdata.TaskExecutionMode.execute);
|
||||||
|
} catch (e) {
|
||||||
|
additionalProps.exceptionOccurred = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
return await service.extractDacpac(this.model.database, this.model.filePath, this.model.database, this.model.version, ownerUri, azdata.TaskExecutionMode.execute);
|
// If result is null which means exception occured, will be adding additional props to the Telemetry
|
||||||
|
if (!result) {
|
||||||
|
additionalProps = { ...additionalProps, ...this.getDacServiceArgsAsProps(service, this.model.database, this.model.filePath, ownerUri) };
|
||||||
|
}
|
||||||
|
additionalProps.extractStatus = result?.success.toString();
|
||||||
|
additionalMeasurements.extractedDacpacFileSizeBytes = await utils.tryGetFileSize(this.model.filePath);
|
||||||
|
additionalMeasurements.totalDurationMs = (new Date().getTime() - extractStartTime);
|
||||||
|
// Extract Dacpac: 'Extract button' clicked in extract summary page, Reporting the event selection to the telemetry
|
||||||
|
this.sendDacServiceTelemetryEvent(TelemetryViews.ExtractDacpac, 'ExtractDacpacOperation', additionalProps, additionalMeasurements);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async export(): Promise<mssql.DacFxResult> {
|
private async export(): Promise<mssql.DacFxResult> {
|
||||||
const service = await this.getService(msSqlProvider);
|
const exportStartTime = new Date().getTime();
|
||||||
const ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
let service: mssql.IDacFxService;
|
||||||
|
let ownerUri: string;
|
||||||
|
let result: mssql.DacFxResult;
|
||||||
|
let additionalProps: TelemetryEventProperties = {};
|
||||||
|
let additionalMeasurements: TelemetryEventMeasures = {};
|
||||||
|
try {
|
||||||
|
service = await this.getService(msSqlProvider);
|
||||||
|
ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
||||||
|
result = await service.exportBacpac(this.model.database, this.model.filePath, ownerUri, azdata.TaskExecutionMode.execute);
|
||||||
|
} catch (e) {
|
||||||
|
additionalProps.exceptionOccurred = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
return await service.exportBacpac(this.model.database, this.model.filePath, ownerUri, azdata.TaskExecutionMode.execute);
|
// If result is null which means exception occured, will be adding additional props to the Telemetry
|
||||||
|
if (!result) {
|
||||||
|
additionalProps = { ...additionalProps, ...this.getDacServiceArgsAsProps(service, this.model.database, this.model.filePath, ownerUri) };
|
||||||
|
}
|
||||||
|
additionalProps.exportStatus = result?.success.toString();
|
||||||
|
additionalMeasurements.exportedBacpacFileSizeBytes = await utils.tryGetFileSize(this.model.filePath);
|
||||||
|
additionalMeasurements.totalDurationMs = (new Date().getTime() - exportStartTime);
|
||||||
|
// Export Bacpac: 'Export button' clicked in Export summary page, Reporting the event selection to the telemetry
|
||||||
|
this.sendDacServiceTelemetryEvent(TelemetryViews.ExportBacpac, 'ExportBacpacOperation', additionalProps, additionalMeasurements);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async import(): Promise<mssql.DacFxResult> {
|
private async import(): Promise<mssql.DacFxResult> {
|
||||||
const service = await this.getService(msSqlProvider);
|
const importStartTime = new Date().getTime();
|
||||||
const ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
let service: mssql.IDacFxService;
|
||||||
|
let ownerUri: string;
|
||||||
|
let result: mssql.DacFxResult;
|
||||||
|
let additionalProps: TelemetryEventProperties = {};
|
||||||
|
let additionalMeasurements: TelemetryEventMeasures = {};
|
||||||
|
try {
|
||||||
|
service = await this.getService(msSqlProvider);
|
||||||
|
ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
||||||
|
result = await service.importBacpac(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.execute);
|
||||||
|
} catch (e) {
|
||||||
|
additionalProps.exceptionOccurred = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
return await service.importBacpac(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.execute);
|
// If result is null which means exception occured, will be adding additional props to the Telemetry
|
||||||
|
if (!result) {
|
||||||
|
additionalProps = { ...additionalProps, ...this.getDacServiceArgsAsProps(service, this.model.database, this.model.filePath, ownerUri) };
|
||||||
|
}
|
||||||
|
additionalProps.importStatus = result?.success.toString();
|
||||||
|
additionalMeasurements.importedBacpacFileSizeBytes = await utils.tryGetFileSize(this.model.filePath);
|
||||||
|
additionalMeasurements.totalDurationMs = (new Date().getTime() - importStartTime);
|
||||||
|
// Import Bacpac: 'Import button' clicked in Import summary page, Reporting the event selection to the telemetry
|
||||||
|
this.sendDacServiceTelemetryEvent(TelemetryViews.ImportBacpac, 'ImportBacpacOperation', additionalProps, additionalMeasurements);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async generateDeployScript(): Promise<mssql.DacFxResult> {
|
public async generateDeployScript(): Promise<mssql.DacFxResult> {
|
||||||
const service = await this.getService(msSqlProvider);
|
const genScriptStartTime = new Date().getTime();
|
||||||
const ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
let service: mssql.IDacFxService;
|
||||||
|
let ownerUri: string;
|
||||||
|
let result: mssql.DacFxResult;
|
||||||
|
let additionalProps: TelemetryEventProperties = {};
|
||||||
|
let additionalMeasurements: TelemetryEventMeasures = {};
|
||||||
|
try {
|
||||||
this.wizard.message = {
|
this.wizard.message = {
|
||||||
text: loc.generatingScriptMessage,
|
text: loc.generatingScriptMessage,
|
||||||
level: azdata.window.MessageLevel.Information,
|
level: azdata.window.MessageLevel.Information,
|
||||||
description: ''
|
description: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = await service.generateDeployScript(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.script);
|
service = await this.getService(msSqlProvider);
|
||||||
|
ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
||||||
|
result = await service.generateDeployScript(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.script);
|
||||||
|
} catch (e) {
|
||||||
|
additionalProps.exceptionOccurred = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
if (!result || !result.success) {
|
if (!result || !result.success) {
|
||||||
vscode.window.showErrorMessage(loc.generateDeployErrorMessage(result?.errorMessage));
|
vscode.window.showErrorMessage(loc.generateDeployErrorMessage(result?.errorMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If result is null which means exception occured, will be adding additional props to the Telemetry
|
||||||
|
if (!result) {
|
||||||
|
additionalProps = { ...additionalProps, ...this.getDacServiceArgsAsProps(service, this.model.database, this.model.filePath, ownerUri) };
|
||||||
|
}
|
||||||
|
additionalProps.isScriptGenerated = result?.success.toString();
|
||||||
|
additionalProps.potentialDataLoss = this.model.potentialDataLoss.toString();
|
||||||
|
additionalMeasurements.deployDacpacFileSizeBytes = await utils.tryGetFileSize(this.model.filePath);
|
||||||
|
additionalMeasurements.totalDurationMs = (new Date().getTime() - genScriptStartTime);
|
||||||
|
// Deploy Dacpac 'generate script' button clicked in DeployPlanPage, Reporting the event selection to the telemetry with fail/sucess status
|
||||||
|
this.sendDacServiceTelemetryEvent(TelemetryViews.DeployDacpac, 'GenerateDeployScriptOperation', additionalProps, additionalMeasurements);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,15 +491,32 @@ export class DataTierApplicationWizard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async generateDeployPlan(): Promise<string> {
|
public async generateDeployPlan(): Promise<string> {
|
||||||
const service = await this.getService(msSqlProvider);
|
const deployPlanStartTime = new Date().getTime();
|
||||||
const ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
let service: mssql.IDacFxService;
|
||||||
|
let ownerUri: string;
|
||||||
const result = await service.generateDeployPlan(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.execute);
|
let result: mssql.GenerateDeployPlanResult;
|
||||||
|
let additionalProps: TelemetryEventProperties = {};
|
||||||
|
let additionalMeasurements: TelemetryEventMeasures = {};
|
||||||
|
try {
|
||||||
|
service = await this.getService(msSqlProvider);
|
||||||
|
ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
||||||
|
result = await service.generateDeployPlan(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.execute);
|
||||||
|
} catch (e) {
|
||||||
|
additionalProps.exceptionOccurred = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
if (!result || !result.success) {
|
if (!result || !result.success) {
|
||||||
vscode.window.showErrorMessage(loc.deployPlanErrorMessage(result?.errorMessage));
|
vscode.window.showErrorMessage(loc.deployPlanErrorMessage(result?.errorMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If result is null which means exception occured, will be adding additional props to the Telemetry
|
||||||
|
if (!result) {
|
||||||
|
additionalProps = { ...additionalProps, ...this.getDacServiceArgsAsProps(service, this.model.database, this.model.filePath, ownerUri) };
|
||||||
|
}
|
||||||
|
additionalProps.isPlanGenerated = result?.success.toString();
|
||||||
|
additionalMeasurements.totalDurationMs = (new Date().getTime() - deployPlanStartTime);
|
||||||
|
// send Generate deploy plan error/succes telemetry event
|
||||||
|
this.sendDacServiceTelemetryEvent(TelemetryViews.DeployPlanPage, 'GenerateDeployPlanOperation', additionalProps, additionalMeasurements);
|
||||||
return result.report;
|
return result.report;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,4 +526,20 @@ export class DataTierApplicationWizard {
|
|||||||
}
|
}
|
||||||
return this.dacfxService;
|
return this.dacfxService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getDacServiceArgsAsProps(service: mssql.IDacFxService, database: string, filePath: string, ownerUri: string): { [k: string]: string } {
|
||||||
|
return {
|
||||||
|
isServiceExist: (!!service).toString(),
|
||||||
|
isDatabaseExists: (!!database).toString(),
|
||||||
|
isFilePathExist: (!!filePath).toString(),
|
||||||
|
isOwnerUriExist: (!!ownerUri).toString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendDacServiceTelemetryEvent(telemetryView: string, telemetryAction: string, additionalProps: TelemetryEventProperties, additionalMeasurements: TelemetryEventMeasures): void {
|
||||||
|
TelemetryReporter.createActionEvent(telemetryView, telemetryAction)
|
||||||
|
.withAdditionalProperties(additionalProps)
|
||||||
|
.withAdditionalMeasurements(additionalMeasurements)
|
||||||
|
.send();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,12 +77,14 @@ export class DeployPlanPage extends DacFxConfigPage {
|
|||||||
this.formBuilder.addFormItem(this.dataLossComponentGroup, { horizontal: true, componentWidth: 400 });
|
this.formBuilder.addFormItem(this.dataLossComponentGroup, { horizontal: true, componentWidth: 400 });
|
||||||
this.dataLossCheckbox.checked = false;
|
this.dataLossCheckbox.checked = false;
|
||||||
this.dataLossCheckbox.enabled = false;
|
this.dataLossCheckbox.enabled = false;
|
||||||
|
this.model.potentialDataLoss = false;
|
||||||
this.formBuilder.removeFormItem(this.noDataLossTextComponent);
|
this.formBuilder.removeFormItem(this.noDataLossTextComponent);
|
||||||
|
|
||||||
this.loader.loading = true;
|
this.loader.loading = true;
|
||||||
this.table.data = [];
|
this.table.data = [];
|
||||||
await this.populateTable();
|
await this.populateTable();
|
||||||
this.loader.loading = false;
|
this.loader.loading = false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +105,7 @@ export class DeployPlanPage extends DacFxConfigPage {
|
|||||||
value: loc.dataLossTextWithCount(result.dataLossAlerts.size)
|
value: loc.dataLossTextWithCount(result.dataLossAlerts.size)
|
||||||
});
|
});
|
||||||
this.dataLossCheckbox.enabled = true;
|
this.dataLossCheckbox.enabled = true;
|
||||||
|
this.model.potentialDataLoss = true;
|
||||||
} else {
|
} else {
|
||||||
// check checkbox to enable Next button and remove checkbox because there won't be any possible data loss
|
// check checkbox to enable Next button and remove checkbox because there won't be any possible data loss
|
||||||
this.dataLossCheckbox.checked = true;
|
this.dataLossCheckbox.checked = true;
|
||||||
|
|||||||
14
src/sql/azdata.proposed.d.ts
vendored
14
src/sql/azdata.proposed.d.ts
vendored
@@ -644,6 +644,13 @@ declare module 'azdata' {
|
|||||||
width?: DialogWidth;
|
width?: DialogWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WizardPage extends ModelViewPanel {
|
||||||
|
/**
|
||||||
|
* An optional name for the page. If provided it will be used for telemetry
|
||||||
|
*/
|
||||||
|
pageName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type DialogWidth = 'narrow' | 'medium' | 'wide' | number;
|
export type DialogWidth = 'narrow' | 'medium' | 'wide' | number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -661,6 +668,13 @@ declare module 'azdata' {
|
|||||||
* @param width The width of the wizard, default value is 'narrow'
|
* @param width The width of the wizard, default value is 'narrow'
|
||||||
*/
|
*/
|
||||||
export function createWizard(title: string, name?: string, width?: DialogWidth): Wizard;
|
export function createWizard(title: string, name?: string, width?: DialogWidth): Wizard;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a wizard page with the given title, for inclusion in a wizard
|
||||||
|
* @param title The title of the page
|
||||||
|
* @param pageName The optional page name parameter will be used for telemetry
|
||||||
|
*/
|
||||||
|
export function createWizardPage(title: string, pageName?: string): WizardPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace workspace {
|
export namespace workspace {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export const FirewallRuleRequested = 'FirewallRuleCreated';
|
|||||||
export const DashboardNavigated = 'DashboardNavigated';
|
export const DashboardNavigated = 'DashboardNavigated';
|
||||||
export const GetDataGridItems = 'GetDataGridItems';
|
export const GetDataGridItems = 'GetDataGridItems';
|
||||||
export const GetDataGridColumns = 'GetDataGridColumns';
|
export const GetDataGridColumns = 'GetDataGridColumns';
|
||||||
|
export const WizardPagesNavigation = 'WizardPagesNavigation';
|
||||||
|
|
||||||
// Telemetry Properties
|
// Telemetry Properties
|
||||||
|
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
|
|||||||
public $setWizardPageDetails(handle: number, details: IModelViewWizardPageDetails): Thenable<void> {
|
public $setWizardPageDetails(handle: number, details: IModelViewWizardPageDetails): Thenable<void> {
|
||||||
let page = this._wizardPages.get(handle);
|
let page = this._wizardPages.get(handle);
|
||||||
if (!page) {
|
if (!page) {
|
||||||
page = new WizardPage(details.title, details.content);
|
page = new WizardPage(details.title, details.content, details.pageName);
|
||||||
page.onValidityChanged(valid => this._proxy.$onPanelValidityChanged(handle, valid));
|
page.onValidityChanged(valid => this._proxy.$onPanelValidityChanged(handle, valid));
|
||||||
this._wizardPages.set(handle, page);
|
this._wizardPages.set(handle, page);
|
||||||
this._wizardPageHandles.set(page, handle);
|
this._wizardPageHandles.set(page, handle);
|
||||||
@@ -161,6 +161,7 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
|
|||||||
page.content = details.content;
|
page.content = details.content;
|
||||||
page.enabled = details.enabled;
|
page.enabled = details.enabled;
|
||||||
page.description = details.description;
|
page.description = details.description;
|
||||||
|
page.pageName = details.pageName;
|
||||||
if (details.customButtons !== undefined) {
|
if (details.customButtons !== undefined) {
|
||||||
page.customButtons = details.customButtons.map(buttonHandle => this.getButton(buttonHandle));
|
page.customButtons = details.customButtons.map(buttonHandle => this.getButton(buttonHandle));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -324,7 +324,8 @@ class WizardPageImpl extends ModelViewPanelImpl implements azdata.window.WizardP
|
|||||||
constructor(public title: string,
|
constructor(public title: string,
|
||||||
extHostModelViewDialog: ExtHostModelViewDialog,
|
extHostModelViewDialog: ExtHostModelViewDialog,
|
||||||
extHostModelView: ExtHostModelViewShape,
|
extHostModelView: ExtHostModelViewShape,
|
||||||
extension: IExtensionDescription) {
|
extension: IExtensionDescription,
|
||||||
|
public pageName?: string) {
|
||||||
super('modelViewWizardPage', extHostModelViewDialog, extHostModelView, extension);
|
super('modelViewWizardPage', extHostModelViewDialog, extHostModelView, extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -758,8 +759,8 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
|
|||||||
this._pageInfoChangedCallbacks.set(handle, callback);
|
this._pageInfoChangedCallbacks.set(handle, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public createWizardPage(title: string, extension?: IExtensionDescription): azdata.window.WizardPage {
|
public createWizardPage(title: string, extension?: IExtensionDescription, pageName?: string): azdata.window.WizardPage {
|
||||||
let page = new WizardPageImpl(title, this, this._extHostModelView, extension);
|
let page = new WizardPageImpl(title, this, this._extHostModelView, extension, pageName);
|
||||||
page.handle = this.getHandle(page);
|
page.handle = this.getHandle(page);
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
@@ -781,7 +782,8 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
|
|||||||
customButtons: page.customButtons ? page.customButtons.map(button => this.getHandle(button)) : undefined,
|
customButtons: page.customButtons ? page.customButtons.map(button => this.getHandle(button)) : undefined,
|
||||||
enabled: page.enabled,
|
enabled: page.enabled,
|
||||||
title: page.title,
|
title: page.title,
|
||||||
description: page.description
|
description: page.description,
|
||||||
|
pageName: page.pageName
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -435,8 +435,8 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
|
|||||||
closeDialog(dialog: azdata.window.Dialog) {
|
closeDialog(dialog: azdata.window.Dialog) {
|
||||||
return extHostModelViewDialog.closeDialog(dialog);
|
return extHostModelViewDialog.closeDialog(dialog);
|
||||||
},
|
},
|
||||||
createWizardPage(title: string): azdata.window.WizardPage {
|
createWizardPage(title: string, pageName?: string): azdata.window.WizardPage {
|
||||||
return extHostModelViewDialog.createWizardPage(title, extension);
|
return extHostModelViewDialog.createWizardPage(title, extension, pageName);
|
||||||
},
|
},
|
||||||
createWizard(title: string, name?: string, width?: azdata.window.DialogWidth): azdata.window.Wizard {
|
createWizard(title: string, name?: string, width?: azdata.window.DialogWidth): azdata.window.Wizard {
|
||||||
return extHostModelViewDialog.createWizard(title, name, width);
|
return extHostModelViewDialog.createWizard(title, name, width);
|
||||||
|
|||||||
@@ -278,6 +278,7 @@ export interface IModelViewWizardPageDetails {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
customButtons: number[];
|
customButtons: number[];
|
||||||
description: string;
|
description: string;
|
||||||
|
pageName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IModelViewWizardDetails {
|
export interface IModelViewWizardDetails {
|
||||||
|
|||||||
@@ -391,14 +391,15 @@ export abstract class Modal extends Disposable implements IThemable {
|
|||||||
/**
|
/**
|
||||||
* Hides the modal and removes key listeners
|
* Hides the modal and removes key listeners
|
||||||
*/
|
*/
|
||||||
protected hide(reason?: string) {
|
protected hide(reason?: string, currentPageName?: string): void {
|
||||||
this._modalShowingContext.get()!.pop();
|
this._modalShowingContext.get()!.pop();
|
||||||
this._bodyContainer!.remove();
|
this._bodyContainer!.remove();
|
||||||
this.disposableStore.clear();
|
this.disposableStore.clear();
|
||||||
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.ModalDialogClosed)
|
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.ModalDialogClosed)
|
||||||
.withAdditionalProperties({
|
.withAdditionalProperties({
|
||||||
name: this._name,
|
name: this._name,
|
||||||
reason: reason
|
reason: reason,
|
||||||
|
currentPageName: currentPageName
|
||||||
})
|
})
|
||||||
.send();
|
.send();
|
||||||
this.restoreKeyboardFocus();
|
this.restoreKeyboardFocus();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||||
import 'vs/css!./media/dialogModal';
|
import 'vs/css!./media/dialogModal';
|
||||||
import { Modal, IModalOptions } from 'sql/workbench/browser/modal/modal';
|
import { Modal, IModalOptions } from 'sql/workbench/browser/modal/modal';
|
||||||
import { Wizard, DialogButton, WizardPage } from 'sql/workbench/services/dialog/common/dialogTypes';
|
import { Wizard, DialogButton, WizardPage } from 'sql/workbench/services/dialog/common/dialogTypes';
|
||||||
@@ -49,14 +50,14 @@ export class WizardModal extends Modal {
|
|||||||
options: IModalOptions,
|
options: IModalOptions,
|
||||||
@ILayoutService layoutService: ILayoutService,
|
@ILayoutService layoutService: ILayoutService,
|
||||||
@IThemeService themeService: IThemeService,
|
@IThemeService themeService: IThemeService,
|
||||||
@IAdsTelemetryService telemetryService: IAdsTelemetryService,
|
@IAdsTelemetryService private _telemetryEventService: IAdsTelemetryService,
|
||||||
@IContextKeyService contextKeyService: IContextKeyService,
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||||
@IClipboardService clipboardService: IClipboardService,
|
@IClipboardService clipboardService: IClipboardService,
|
||||||
@ILogService logService: ILogService,
|
@ILogService logService: ILogService,
|
||||||
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
|
@ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
|
||||||
) {
|
) {
|
||||||
super(_wizard.title, _wizard.name, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, options);
|
super(_wizard.title, _wizard.name, _telemetryEventService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, options);
|
||||||
this._useDefaultMessageBoxLocation = false;
|
this._useDefaultMessageBoxLocation = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +176,7 @@ export class WizardModal extends Modal {
|
|||||||
|
|
||||||
public async showPage(index: number, validate: boolean = true, focus: boolean = false, readHeader: boolean = true): Promise<void> {
|
public async showPage(index: number, validate: boolean = true, focus: boolean = false, readHeader: boolean = true): Promise<void> {
|
||||||
let pageToShow = this._wizard.pages[index];
|
let pageToShow = this._wizard.pages[index];
|
||||||
|
const prevPageIndex = this._wizard.currentPage;
|
||||||
if (!pageToShow) {
|
if (!pageToShow) {
|
||||||
this.done(validate).catch(err => onUnexpectedError(err));
|
this.done(validate).catch(err => onUnexpectedError(err));
|
||||||
return;
|
return;
|
||||||
@@ -209,6 +211,15 @@ export class WizardModal extends Modal {
|
|||||||
this._doneButton.enabled = this._wizard.doneButton.enabled && pageToShow.valid;
|
this._doneButton.enabled = this._wizard.doneButton.enabled && pageToShow.valid;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (index !== prevPageIndex) {
|
||||||
|
this._telemetryEventService.createActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.WizardPagesNavigation)
|
||||||
|
.withAdditionalProperties({
|
||||||
|
wizardName: this._wizard.name,
|
||||||
|
pageNavigationFrom: this._wizard.pages[prevPageIndex].pageName ?? prevPageIndex,
|
||||||
|
pageNavigationTo: this._wizard.pages[index].pageName ?? index
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setButtonsForPage(index: number) {
|
private setButtonsForPage(index: number) {
|
||||||
@@ -268,9 +279,10 @@ export class WizardModal extends Modal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public cancel(): void {
|
public cancel(): void {
|
||||||
|
const currentPage = this._wizard.pages[this._wizard.currentPage];
|
||||||
this._onCancel.fire();
|
this._onCancel.fire();
|
||||||
this.dispose();
|
this.dispose();
|
||||||
this.hide('cancel');
|
this.hide('cancel', currentPage.pageName ?? this._wizard.currentPage.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async validateNavigation(newPage: number): Promise<boolean> {
|
private async validateNavigation(newPage: number): Promise<boolean> {
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ export class WizardPage extends DialogTab {
|
|||||||
private _onUpdate: Emitter<void> = new Emitter<void>();
|
private _onUpdate: Emitter<void> = new Emitter<void>();
|
||||||
public readonly onUpdate: Event<void> = this._onUpdate.event;
|
public readonly onUpdate: Event<void> = this._onUpdate.event;
|
||||||
|
|
||||||
constructor(public title: string, content?: string) {
|
constructor(public title: string, content?: string, public pageName?: string) {
|
||||||
super(title, content);
|
super(title, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -140,14 +140,16 @@ suite('MainThreadModelViewDialog Tests', () => {
|
|||||||
content: 'content1',
|
content: 'content1',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
customButtons: [],
|
customButtons: [],
|
||||||
description: 'description1'
|
description: 'description1',
|
||||||
|
pageName: 'pageName1'
|
||||||
};
|
};
|
||||||
page2Details = {
|
page2Details = {
|
||||||
title: 'page2',
|
title: 'page2',
|
||||||
content: 'content2',
|
content: 'content2',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
customButtons: [button1Handle, button2Handle],
|
customButtons: [button1Handle, button2Handle],
|
||||||
description: 'description2'
|
description: 'description2',
|
||||||
|
pageName: undefined
|
||||||
};
|
};
|
||||||
wizardDetails = {
|
wizardDetails = {
|
||||||
backButton: backButtonHandle,
|
backButton: backButtonHandle,
|
||||||
@@ -302,7 +304,8 @@ suite('MainThreadModelViewDialog Tests', () => {
|
|||||||
content: 'content_3',
|
content: 'content_3',
|
||||||
customButtons: [],
|
customButtons: [],
|
||||||
enabled: true,
|
enabled: true,
|
||||||
description: undefined
|
description: undefined,
|
||||||
|
pageName: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
// If I open the wizard and then add a page
|
// If I open the wizard and then add a page
|
||||||
|
|||||||
Reference in New Issue
Block a user