diff --git a/extensions/sql-migration/package.json b/extensions/sql-migration/package.json index a4fb825a46..0192d9f493 100644 --- a/extensions/sql-migration/package.json +++ b/extensions/sql-migration/package.json @@ -2,7 +2,7 @@ "name": "sql-migration", "displayName": "%displayName%", "description": "%description%", - "version": "1.4.1", + "version": "1.4.2", "publisher": "Microsoft", "preview": false, "license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt", diff --git a/extensions/sql-migration/src/api/sqlUtils.ts b/extensions/sql-migration/src/api/sqlUtils.ts index 797f157d71..8a9597ac90 100644 --- a/extensions/sql-migration/src/api/sqlUtils.ts +++ b/extensions/sql-migration/src/api/sqlUtils.ts @@ -9,6 +9,7 @@ import { AzureSqlDatabase, AzureSqlDatabaseServer } from './azure'; import { generateGuid } from './utils'; import * as utils from '../api/utils'; import { TelemetryAction, TelemetryViews, logError } from '../telemetry'; +import * as constants from '../constants/strings'; const query_database_tables_sql = ` SELECT @@ -489,7 +490,10 @@ export async function collectTargetLogins( return results.rows.map(row => getSqlString(row[0])) ?? []; } - throw new Error(result.errorMessage); + const errorMessage = constants.COLLECTING_TARGET_LOGINS_FAILED(result.errorCode ?? 0); + const error = new Error(result.errorMessage); + logError(TelemetryViews.LoginMigrationWizard, errorMessage, error); + throw error; } export async function isSourceConnectionSysAdmin(): Promise { diff --git a/extensions/sql-migration/src/constants/strings.ts b/extensions/sql-migration/src/constants/strings.ts index 615f9b5aee..bb3b971329 100644 --- a/extensions/sql-migration/src/constants/strings.ts +++ b/extensions/sql-migration/src/constants/strings.ts @@ -389,7 +389,9 @@ export const MIGRATE_LOGINS = localize('sql.login.migration.steps.migrate.logins export const ESTABLISH_USER_MAPPINGS = localize('sql.login.migration.steps.migrate.logins', "Establish user mappings"); export const MIGRATE_SERVER_ROLES_AND_SET_PERMISSIONS = localize('sql.login.migration.steps.migrate.logins', "Migrate server roles, set login and server permissions"); export const LOGIN_MIGRATION_COMPLETED = localize('sql.login.migration.steps.migrate.logins', "Login migration completed"); - +export function COLLECTING_TARGET_LOGINS_FAILED(errorCode: number): string { + return localize('sql.login.migration.collecting.target.logins.failed', "Collecting target login failed with error code {0}", errorCode); +} // Azure SQL Target export const AZURE_SQL_TARGET_PAGE_TITLE = localize('sql.migration.wizard.target.title', "Azure SQL target"); diff --git a/extensions/sql-migration/src/models/loginMigrationModel.ts b/extensions/sql-migration/src/models/loginMigrationModel.ts index 8ad95975eb..55b3daf5d1 100644 --- a/extensions/sql-migration/src/models/loginMigrationModel.ts +++ b/extensions/sql-migration/src/models/loginMigrationModel.ts @@ -15,6 +15,7 @@ type ExceptionMap = { [login: string]: any } export enum LoginType { Windows_Login = 'windows_login', SQL_Login = 'sql_login', + Mixed_Mode = 'mixed_mode', } export enum LoginMigrationStep { @@ -64,6 +65,8 @@ export class LoginMigrationModel { public loginMigrationsResult!: contracts.StartLoginMigrationResult; public loginMigrationsError: any; public loginsForMigration!: LoginTableInfo[]; + public errorCountMap: Map = new Map(); + public durationPerStep: Map = new Map(); private _currentStepIdx: number = 0; private _logins: Map; private _loginMigrationSteps: LoginMigrationStep[] = []; @@ -82,6 +85,32 @@ export class LoginMigrationModel { return this._currentStepIdx === this._loginMigrationSteps.length; } + public get selectedWindowsLogins(): boolean { + return this.loginsForMigration.some(logins => logins.loginType.toLocaleLowerCase() === LoginType.Windows_Login); + } + + public get selectedAllWindowsLogins(): boolean { + return this.loginsForMigration.every(logins => logins.loginType.toLocaleLowerCase() === LoginType.Windows_Login); + } + + public get selectedAllSQLLogins(): boolean { + return this.loginsForMigration.every(logins => logins.loginType.toLocaleLowerCase() === LoginType.SQL_Login); + } + + public get loginsAuthType(): LoginType { + if (this.selectedAllWindowsLogins) { + return LoginType.Windows_Login; + } else if (this.selectedAllSQLLogins) { + return LoginType.SQL_Login; + } + + return LoginType.Mixed_Mode; + } + + public get hasSystemError(): boolean { + return this.loginMigrationsError ? true : false; + } + public async MigrateLogins(stateMachine: MigrationStateModel): Promise { this.addNewLogins(stateMachine._loginMigrationModel.loginsForMigration.map(row => row.loginName)); return await this.runLoginMigrationStep(LoginMigrationStep.MigrateLogins, stateMachine); @@ -102,7 +131,7 @@ export class LoginMigrationModel { } - public GetLoginMigrationResults(loginName: string): MultiStepResult[] { + public GetDisplayResults(loginName: string): MultiStepResult[] { let loginResults: MultiStepResult[] = []; let login = this.getLogin(loginName); @@ -129,6 +158,15 @@ export class LoginMigrationModel { return loginResults; } + private setErrorCountMapPerStep(step: LoginMigrationStep, result: contracts.StartLoginMigrationResult) { + const errorCount = result.exceptionMap ? Object.keys(result.exceptionMap).length : 0; + this.errorCountMap.set(LoginMigrationStep[step], errorCount); + } + + private setDurationPerStep(step: LoginMigrationStep, result: contracts.StartLoginMigrationResult) { + this.durationPerStep.set(LoginMigrationStep[step], result.elapsedTime); + } + private setLoginMigrationSteps(steps: LoginMigrationStep[] = []) { this._loginMigrationSteps = []; @@ -159,6 +197,10 @@ export class LoginMigrationModel { this.markLoginStatus(loginName, loginStatus); } } + + this.updateLoginMigrationResults(newResult); + this.setErrorCountMapPerStep(step, newResult); + this.setDurationPerStep(step, newResult); } private updateLoginMigrationResults(newResult: contracts.StartLoginMigrationResult): void { @@ -194,7 +236,6 @@ export class LoginMigrationModel { stateMachine._aadDomainName ))!; - this.updateLoginMigrationResults(response); this.addLoginMigrationResults(LoginMigrationStep.MigrateLogins, response); return true; @@ -215,9 +256,8 @@ export class LoginMigrationModel { stateMachine._aadDomainName ))!; - this.updateLoginMigrationResults(response); this.addLoginMigrationResults(LoginMigrationStep.EstablishUserMapping, response); - return false; + return true; } catch (error) { logError(TelemetryViews.LoginMigrationWizard, 'EstablishingUserMappingFailed', error); @@ -236,16 +276,14 @@ export class LoginMigrationModel { stateMachine._aadDomainName ))!; - this.updateLoginMigrationResults(response); this.addLoginMigrationResults(LoginMigrationStep.MigrateServerRolesAndSetPermissions, response); - return false; + return true; } catch (error) { logError(TelemetryViews.LoginMigrationWizard, 'MigratingServerRolesAndSettingPermissionsFailed', error); this.reportException(LoginMigrationStep.MigrateServerRolesAndSetPermissions, error); this.loginMigrationsError = error; - return true; - + return false; } } diff --git a/extensions/sql-migration/src/telemetry.ts b/extensions/sql-migration/src/telemetry.ts index 50dd267ebe..41829136f5 100644 --- a/extensions/sql-migration/src/telemetry.ts +++ b/extensions/sql-migration/src/telemetry.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import AdsTelemetryReporter, { TelemetryEventMeasures, TelemetryEventProperties } from '@microsoft/ads-extension-telemetry'; +import { MigrationStateModel } from './models/stateMachine'; const packageJson = require('../package.json'); let packageInfo = { name: packageJson.name, @@ -38,6 +39,9 @@ export enum TelemetryViews { Utils = 'Utils', LoginMigrationWizardController = 'LoginMigrationWizardController', LoginMigrationWizard = 'LoginMigrationWizard', + LoginMigrationTargetSelectionPage = 'LoginMigrationTargetSelectionPage', + LoginMigrationSelectorPage = 'LoginMigrationSelectorPage', + LoginMigrationStatusPage = 'LoginMigrationStatusPage', TdeConfigurationDialog = 'TdeConfigurationDialog', } @@ -64,7 +68,11 @@ export enum TelemetryAction { GetInstanceRequirements = 'GetInstanceRequirements', StartDataCollection = 'StartDataCollection', StopDataCollection = 'StopDataCollection', - GetDatabasesListFailed = 'GetDatabasesListFailed' + GetDatabasesListFailed = 'GetDatabasesListFailed', + ConnectToTarget = 'ConnectToTarget', + OpenLoginMigrationWizard = 'OpenLoginMigrationWizard', + LoginMigrationStarted = 'LoginMigrationStarted', + LoginMigrationCompleted = 'LoginMigrationCompleted', } export enum TelemetryErrorName { @@ -82,3 +90,26 @@ export function sendSqlMigrationActionEvent(telemetryView: TelemetryViews, telem .withAdditionalMeasurements(additionalMeasurements) .send(); } + +export function getTelemetryProps(migrationStateModel: MigrationStateModel): TelemetryEventProperties { + return { + 'sessionId': migrationStateModel._sessionId, + 'subscriptionId': migrationStateModel._targetSubscription?.id, + 'resourceGroup': migrationStateModel._resourceGroup?.name, + 'targetType': migrationStateModel._targetType, + 'tenantId': migrationStateModel._azureAccount?.properties?.tenants[0]?.id, + }; +} + +export function sendButtonClickEvent(migrationStateModel: MigrationStateModel, telemetryView: TelemetryViews, buttonPressed: TelemetryAction, pageTitle: string, newPageTitle: string): void { + sendSqlMigrationActionEvent( + telemetryView, + TelemetryAction.PageButtonClick, + { + ...getTelemetryProps(migrationStateModel), + 'buttonPressed': buttonPressed, + 'pageTitle': pageTitle, + 'newPageTitle': newPageTitle + }, + {}); +} diff --git a/extensions/sql-migration/src/wizard/loginMigrationStatusPage.ts b/extensions/sql-migration/src/wizard/loginMigrationStatusPage.ts index 6308856161..6bea50f6dc 100644 --- a/extensions/sql-migration/src/wizard/loginMigrationStatusPage.ts +++ b/extensions/sql-migration/src/wizard/loginMigrationStatusPage.ts @@ -13,6 +13,7 @@ import * as styles from '../constants/styles'; import { IconPathHelper } from '../constants/iconPathHelper'; import { LoginMigrationStatusCodes } from '../constants/helper'; import { MultiStepStatusDialog } from '../dialog/generic/multiStepStatusDialog'; +import { getTelemetryProps, logError, sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews } from '../telemetry'; export class LoginMigrationStatusPage extends MigrationWizardPage { private _view!: azdata.ModelView; @@ -92,10 +93,13 @@ export class LoginMigrationStatusPage extends MigrationWizardPage { }; this._progressLoader.loading = false; + logError(TelemetryViews.LoginMigrationWizard, 'LoginMigrationFailed', 'Login Migrations Internal Server Error'); } + this._logMigrationResult(); await this._loadMigratingLoginsList(this.migrationStateModel); await this._filterTableList(''); + this.wizard.doneButton.enabled = true; } public async onPageLeave(): Promise { @@ -307,6 +311,7 @@ export class LoginMigrationStatusPage extends MigrationWizardPage { status = LoginMigrationStatusCodes.Succeeded; title = constants.LOGIN_MIGRATION_STATUS_SUCCEEDED; var didLoginFail = Object.keys(stateMachine._loginMigrationModel.loginMigrationsResult.exceptionMap).some(key => key.toLocaleLowerCase() === loginName.toLocaleLowerCase()); + if (didLoginFail) { status = LoginMigrationStatusCodes.Failed; title = constants.LOGIN_MIGRATION_STATUS_FAILED; @@ -339,60 +344,32 @@ export class LoginMigrationStatusPage extends MigrationWizardPage { return this._migratingLoginsTable?.data?.length || 0; } - private async _runLoginMigrations(): Promise { - this._progressLoader.loading = true; + private async _updateMigrationProgressDetails(message: string) { + await this._migrationProgressDetails.updateProperties({ + 'value': message + }); + } + private async _hideMigrationProgressDetails() { + await this._migrationProgressDetails.updateProperties({ + 'CSSStyles': { 'display': 'none' }, + }); + } + + private async _markMigrationStart() { if (this.migrationStateModel._targetServerInstance) { await this._migrationProgress.updateProperties({ 'text': constants.LOGIN_MIGRATIONS_STATUS_PAGE_DESCRIPTION(this._getTotalNumberOfLogins(), this.migrationStateModel.GetTargetType(), this.migrationStateModel._targetServerInstance.name) }); } - await this._migrationProgressDetails.updateProperties({ - 'value': constants.STARTING_LOGIN_MIGRATION - }); + this._progressLoader.loading = true; + await this._updateMigrationProgressDetails(constants.STARTING_LOGIN_MIGRATION); + this._logMigrationStart(); + } - var result = await this.migrationStateModel._loginMigrationModel.MigrateLogins(this.migrationStateModel); - - if (!result) { - await this._migrationProgressDetails.updateProperties({ - 'value': constants.STARTING_LOGIN_MIGRATION_FAILED - }); - - return false; - } - - await this._migrationProgressDetails.updateProperties({ - 'value': constants.ESTABLISHING_USER_MAPPINGS - }); - - result = await this.migrationStateModel._loginMigrationModel.EstablishUserMappings(this.migrationStateModel); - - if (!result) { - await this._migrationProgressDetails.updateProperties({ - 'value': constants.ESTABLISHING_USER_MAPPINGS_FAILED - }); - - return false; - } - - await this._migrationProgressDetails.updateProperties({ - 'value': constants.MIGRATING_SERVER_ROLES_AND_SET_PERMISSIONS - }); - - result = await this.migrationStateModel._loginMigrationModel.MigrateServerRolesAndSetPermissions(this.migrationStateModel); - - if (!result) { - await this._migrationProgressDetails.updateProperties({ - 'value': constants.MIGRATING_SERVER_ROLES_AND_SET_PERMISSIONS_FAILED - }); - - return false; - } - - await this._migrationProgressDetails.updateProperties({ - 'CSSStyles': { 'display': 'none' }, - }); + private async _markMigrationSuccess() { + await this._hideMigrationProgressDetails(); if (this.migrationStateModel._targetServerInstance) { await this._migrationProgress.updateProperties({ @@ -407,9 +384,35 @@ export class LoginMigrationStatusPage extends MigrationWizardPage { } this._progressLoader.loading = false; + } - this.wizard.doneButton.enabled = true; - return result; + private async _runLoginMigrations(): Promise { + await this._markMigrationStart(); + var result = await this.migrationStateModel._loginMigrationModel.MigrateLogins(this.migrationStateModel); + + if (!result) { + await this._updateMigrationProgressDetails(constants.STARTING_LOGIN_MIGRATION_FAILED); + return false; + } + + await this._updateMigrationProgressDetails(constants.ESTABLISHING_USER_MAPPINGS); + result = await this.migrationStateModel._loginMigrationModel.EstablishUserMappings(this.migrationStateModel); + + if (!result) { + await this._updateMigrationProgressDetails(constants.ESTABLISHING_USER_MAPPINGS_FAILED); + return false; + } + + await this._updateMigrationProgressDetails(constants.MIGRATING_SERVER_ROLES_AND_SET_PERMISSIONS); + result = await this.migrationStateModel._loginMigrationModel.MigrateServerRolesAndSetPermissions(this.migrationStateModel); + + if (!result) { + await this._updateMigrationProgressDetails(constants.MIGRATING_SERVER_ROLES_AND_SET_PERMISSIONS_FAILED); + return false; + } + + await this._markMigrationSuccess(); + return true; } private async _showLoginDetailsDialog(loginName: string): Promise { @@ -417,9 +420,39 @@ export class LoginMigrationStatusPage extends MigrationWizardPage { const dialog = new MultiStepStatusDialog( () => { }); - const loginResults = this.migrationStateModel._loginMigrationModel.GetLoginMigrationResults(loginName); + const loginResults = this.migrationStateModel._loginMigrationModel.GetDisplayResults(loginName); const isMigrationComplete = this.migrationStateModel._loginMigrationModel.isMigrationComplete; await dialog.openDialog(constants.LOGIN_MIGRATIONS_LOGIN_STATUS_DETAILS_TITLE(loginName), loginResults, isMigrationComplete); } + + private _logMigrationStart(): void { + sendSqlMigrationActionEvent( + TelemetryViews.LoginMigrationStatusPage, + TelemetryAction.LoginMigrationStarted, + { + ...getTelemetryProps(this.migrationStateModel), + 'loginsAuthType': this.migrationStateModel._loginMigrationModel.loginsAuthType, + }, + { + 'numberLogins': this.migrationStateModel._loginMigrationModel.loginsForMigration.length, + } + ); + } + + private _logMigrationResult(): void { + sendSqlMigrationActionEvent( + TelemetryViews.LoginMigrationStatusPage, + TelemetryAction.LoginMigrationCompleted, + { + ...getTelemetryProps(this.migrationStateModel), + 'loginsAuthType': this.migrationStateModel._loginMigrationModel.loginsAuthType, + 'numberLoginsFailingPerStep': JSON.stringify(Array.from(this.migrationStateModel._loginMigrationModel.errorCountMap)), + 'durationPerStepTimestamp': JSON.stringify(Array.from(this.migrationStateModel._loginMigrationModel.durationPerStep)), + 'hasSystemError': JSON.stringify(this.migrationStateModel._loginMigrationModel.hasSystemError), + // AKMA TODO: add error code string count map + }, + {} + ); + } } diff --git a/extensions/sql-migration/src/wizard/loginMigrationTargetSelectionPage.ts b/extensions/sql-migration/src/wizard/loginMigrationTargetSelectionPage.ts index e994e83b55..369ab26a5a 100644 --- a/extensions/sql-migration/src/wizard/loginMigrationTargetSelectionPage.ts +++ b/extensions/sql-migration/src/wizard/loginMigrationTargetSelectionPage.ts @@ -16,6 +16,7 @@ import { azureResource } from 'azurecore'; import { AzureSqlDatabaseServer, getVMInstanceView, SqlVMServer } from '../api/azure'; import { collectSourceLogins, collectTargetLogins, getSourceConnectionId, getSourceConnectionProfile, isSourceConnectionSysAdmin, LoginTableInfo } from '../api/sqlUtils'; import { NetworkInterfaceModel } from '../api/dataModels/azure/networkInterfaceModel'; +import { getTelemetryProps, logError, sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews } from '../telemetry'; export class LoginMigrationTargetSelectionPage extends MigrationWizardPage { private _view!: azdata.ModelView; @@ -606,6 +607,7 @@ export class LoginMigrationTargetSelectionPage extends MigrationWizardPage { const userName = this.migrationStateModel._targetUserName; const password = this.migrationStateModel._targetPassword; const loginsOnTarget: string[] = []; + let connectionSuccessful = false; if (targetDatabaseServer && userName && password) { try { connectionButtonLoadingContainer.loading = true; @@ -622,6 +624,7 @@ export class LoginMigrationTargetSelectionPage extends MigrationWizardPage { this.migrationStateModel._loginMigrationModel.loginsOnTarget = loginsOnTarget; await this._showConnectionResults(loginsOnTarget); + connectionSuccessful = true; } catch (error) { this.wizard.message = { level: azdata.window.MessageLevel.Error, @@ -631,9 +634,20 @@ export class LoginMigrationTargetSelectionPage extends MigrationWizardPage { await this._showConnectionResults( loginsOnTarget, constants.AZURE_SQL_TARGET_CONNECTION_ERROR_TITLE); + + logError(TelemetryViews.LoginMigrationTargetSelectionPage, 'ConnectingToTargetFailed', error); + connectionSuccessful = false; } finally { connectionButtonLoadingContainer.loading = false; + sendSqlMigrationActionEvent( + TelemetryViews.LoginMigrationTargetSelectionPage, + TelemetryAction.ConnectToTarget, + { + ...getTelemetryProps(this.migrationStateModel), + 'connectionSuccessful': JSON.stringify(connectionSuccessful) + }, + {}); } } })); diff --git a/extensions/sql-migration/src/wizard/loginSelectorPage.ts b/extensions/sql-migration/src/wizard/loginSelectorPage.ts index ee74321eb3..292754f134 100644 --- a/extensions/sql-migration/src/wizard/loginSelectorPage.ts +++ b/extensions/sql-migration/src/wizard/loginSelectorPage.ts @@ -13,7 +13,8 @@ import * as styles from '../constants/styles'; import { collectSourceLogins, collectTargetLogins, getSourceConnectionId, LoginTableInfo } from '../api/sqlUtils'; import { IconPathHelper } from '../constants/iconPathHelper'; import * as utils from '../api/utils'; -import { LoginType } from '../models/loginMigrationModel'; +import { logError, TelemetryViews } from '../telemetry'; + export class LoginSelectorPage extends MigrationWizardPage { private _view!: azdata.ModelView; @@ -74,7 +75,7 @@ export class LoginSelectorPage extends MigrationWizardPage { return false; } - if (this.selectedWindowsLogins() && !this.migrationStateModel._aadDomainName) { + if (this.migrationStateModel._loginMigrationModel.selectedWindowsLogins && !this.migrationStateModel._aadDomainName) { this.wizard.message = { text: constants.ENTER_AAD_DOMAIN_NAME, level: azdata.window.MessageLevel.Error @@ -365,6 +366,8 @@ export class LoginSelectorPage extends MigrationWizardPage { text: constants.LOGIN_MIGRATIONS_GET_LOGINS_ERROR_TITLE('source'), description: constants.LOGIN_MIGRATIONS_GET_LOGINS_ERROR(error.message), }; + + logError(TelemetryViews.LoginMigrationWizard, 'CollectingSourceLoginsFailed', error); } } @@ -396,6 +399,8 @@ export class LoginSelectorPage extends MigrationWizardPage { text: constants.LOGIN_MIGRATIONS_GET_LOGINS_ERROR_TITLE('target'), description: constants.LOGIN_MIGRATIONS_GET_LOGINS_ERROR(error.message), }; + + logError(TelemetryViews.LoginMigrationWizard, 'CollectingTargetLoginsFailed', error); } } @@ -471,8 +476,11 @@ export class LoginSelectorPage extends MigrationWizardPage { || []; } - private selectedWindowsLogins(): boolean { - return this.selectedLogins().some(logins => logins.loginType.toLocaleLowerCase() === LoginType.Windows_Login); + private async refreshAADInputBox() { + // Display AAD Domain Name input box only if windows logins selected, else disable + const selectedWindowsLogins = this.migrationStateModel._loginMigrationModel.selectedWindowsLogins; + await utils.updateControlDisplay(this._aadDomainNameContainer, selectedWindowsLogins); + await this._loginSelectorTable.updateProperty("height", selectedWindowsLogins ? 600 : 650); } private async updateValuesOnSelection() { @@ -483,12 +491,9 @@ export class LoginSelectorPage extends MigrationWizardPage { this._loginSelectorTable.data?.length || 0) }); - // Display AAD Domain Name input box if windows logins selected, else disable - const hasSelectedWindowsLogins = this.selectedWindowsLogins() - await utils.updateControlDisplay(this._aadDomainNameContainer, hasSelectedWindowsLogins); - await this._loginSelectorTable.updateProperty("height", hasSelectedWindowsLogins ? 600 : 650); - this.migrationStateModel._loginMigrationModel.loginsForMigration = selectedLogins; + this.migrationStateModel._loginMigrationModel.loginsForMigration = selectedLogins; + await this.refreshAADInputBox(); this.updateNextButton(); } diff --git a/extensions/sql-migration/src/wizard/wizardController.ts b/extensions/sql-migration/src/wizard/wizardController.ts index 8da91f93cc..b97d882dc3 100644 --- a/extensions/sql-migration/src/wizard/wizardController.ts +++ b/extensions/sql-migration/src/wizard/wizardController.ts @@ -16,7 +16,7 @@ import { SummaryPage } from './summaryPage'; import { LoginMigrationStatusPage } from './loginMigrationStatusPage'; import { DatabaseSelectorPage } from './databaseSelectorPage'; import { LoginSelectorPage } from './loginSelectorPage'; -import { sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews, logError } from '../telemetry'; +import { sendSqlMigrationActionEvent, sendButtonClickEvent, TelemetryAction, TelemetryViews, logError, getTelemetryProps } from '../telemetry'; import * as styles from '../constants/styles'; import { MigrationLocalStorage, MigrationServiceContext } from '../models/migrationLocalStorage'; import { azureResource } from 'azurecore'; @@ -131,7 +131,7 @@ export class WizardController { async (pageChangeInfo: azdata.window.WizardPageChangeInfo) => { const newPage = pageChangeInfo.newPage; const lastPage = pageChangeInfo.lastPage; - this.sendPageButtonClickEvent(pageChangeInfo) + this.sendPageButtonClickEvent(TelemetryViews.SqlMigrationWizard, pageChangeInfo) .catch(e => logError( TelemetryViews.MigrationWizardController, 'ErrorSendingPageButtonClick', e)); @@ -163,7 +163,7 @@ export class WizardController { TelemetryViews.SqlMigrationWizard, TelemetryAction.PageButtonClick, { - ...this.getTelemetryProps(), + ...getTelemetryProps(this._model), 'buttonPressed': TelemetryAction.Cancel, 'pageTitle': this._wizardObject.pages[this._wizardObject.currentPage].title }, @@ -182,7 +182,7 @@ export class WizardController { TelemetryViews.SqlMigrationWizard, TelemetryAction.PageButtonClick, { - ...this.getTelemetryProps(), + ...getTelemetryProps(this._model), 'buttonPressed': TelemetryAction.Done, 'pageTitle': this._wizardObject.pages[this._wizardObject.currentPage].title }, @@ -216,12 +216,16 @@ export class WizardController { wizardSetupPromises.push(...pages.map(p => p.registerWizardContent())); wizardSetupPromises.push(this._wizardObject.open()); + // Emit telemetry for starting login migration wizard + const firstPageTitle = this._wizardObject.pages.length > 0 ? this._wizardObject.pages[0].title : ""; + sendButtonClickEvent(this._model, TelemetryViews.LoginMigrationWizard, TelemetryAction.OpenLoginMigrationWizard, "", firstPageTitle); + this._model.extensionContext.subscriptions.push( this._wizardObject.onPageChanged( async (pageChangeInfo: azdata.window.WizardPageChangeInfo) => { const newPage = pageChangeInfo.newPage; const lastPage = pageChangeInfo.lastPage; - this.sendPageButtonClickEvent(pageChangeInfo) + this.sendPageButtonClickEvent(TelemetryViews.LoginMigrationWizard, pageChangeInfo) .catch(e => logError( TelemetryViews.LoginMigrationWizardController, 'ErrorSendingPageButtonClick', e)); @@ -243,12 +247,25 @@ export class WizardController { TelemetryViews.LoginMigrationWizard, TelemetryAction.PageButtonClick, { - ...this.getTelemetryProps(), + ...getTelemetryProps(this._model), 'buttonPressed': TelemetryAction.Cancel, 'pageTitle': this._wizardObject.pages[this._wizardObject.currentPage].title }, {}); })); + + this._disposables.push( + this._wizardObject.doneButton.onClick(async (e) => { + sendSqlMigrationActionEvent( + TelemetryViews.LoginMigrationWizard, + TelemetryAction.PageButtonClick, + { + ...getTelemetryProps(this._model), + 'buttonPressed': TelemetryAction.Done, + 'pageTitle': this._wizardObject.pages[this._wizardObject.currentPage].title + }, + {}); + })); } private async updateServiceContext( @@ -308,31 +325,15 @@ export class WizardController { return undefined; } - private async sendPageButtonClickEvent(pageChangeInfo: azdata.window.WizardPageChangeInfo) { + private async sendPageButtonClickEvent(telemetryView: TelemetryViews, pageChangeInfo: azdata.window.WizardPageChangeInfo) { const buttonPressed = pageChangeInfo.newPage > pageChangeInfo.lastPage ? TelemetryAction.Next : TelemetryAction.Prev; const pageTitle = this._wizardObject.pages[pageChangeInfo.lastPage]?.title; - sendSqlMigrationActionEvent( - TelemetryViews.SqlMigrationWizard, - TelemetryAction.PageButtonClick, - { - ...this.getTelemetryProps(), - 'buttonPressed': buttonPressed, - 'pageTitle': pageTitle - }, - {}); + const newPageTitle = this._wizardObject.pages[pageChangeInfo.newPage]?.title; + sendButtonClickEvent(this._model, telemetryView, buttonPressed, pageTitle, newPageTitle); } - private getTelemetryProps() { - return { - 'sessionId': this._model._sessionId, - 'subscriptionId': this._model._targetSubscription?.id, - 'resourceGroup': this._model._resourceGroup?.name, - 'targetType': this._model._targetType, - 'tenantId': this._model?._azureAccount?.properties?.tenants[0]?.id - }; - } } export function createInformationRow(