From e741fa0bbda28af14c73e8ead19ba1359ba57ae9 Mon Sep 17 00:00:00 2001 From: junierch <109680247+junierch@users.noreply.github.com> Date: Mon, 27 Mar 2023 17:48:47 -0400 Subject: [PATCH] telemetry for tde user actions (#22474) * telemetry for user actions * remove unused action * try catch around admin function --- extensions/sql-migration/src/api/utils.ts | 18 ++++++++++ .../tdeConfiguration/tdeMigrationDialog.ts | 34 +++++++++++++++---- .../sql-migration/src/models/stateMachine.ts | 2 +- .../sql-migration/src/service/contracts.ts | 3 +- .../sql-migration/src/service/features.ts | 8 ++--- extensions/sql-migration/src/telemetry.ts | 10 +++--- .../src/wizard/skuRecommendationPage.ts | 16 +++++++-- 7 files changed, 71 insertions(+), 20 deletions(-) diff --git a/extensions/sql-migration/src/api/utils.ts b/extensions/sql-migration/src/api/utils.ts index 46ebd992f8..c5682b5c68 100644 --- a/extensions/sql-migration/src/api/utils.ts +++ b/extensions/sql-migration/src/api/utils.ts @@ -13,6 +13,7 @@ import * as constants from '../constants/strings'; import { logError, TelemetryViews } from '../telemetry'; import { AdsMigrationStatus } from '../dashboard/tabBase'; import { getMigrationMode, getMigrationStatus, getMigrationTargetType, hasRestoreBlockingReason, PipelineStatusCodes } from '../constants/helper'; +import * as os from 'os'; export type TargetServerType = azure.SqlVMServer | azureResource.AzureSqlManagedInstance | azure.AzureSqlDatabaseServer; @@ -923,3 +924,20 @@ export async function promptUserForFolder(): Promise { return ''; } + +export function isWindows(): boolean { return (os.platform() === 'win32') } + +export async function isAdmin(): Promise { + let isAdmin: boolean = false; + try { + if (isWindows()) { + isAdmin = (await import('native-is-elevated'))(); + } else { + isAdmin = process.getuid() === 0; + } + } catch (e) { + //Ignore error and return false; + } + + return isAdmin; +} diff --git a/extensions/sql-migration/src/dialog/tdeConfiguration/tdeMigrationDialog.ts b/extensions/sql-migration/src/dialog/tdeConfiguration/tdeMigrationDialog.ts index f0c4bebf1c..c64babde9e 100644 --- a/extensions/sql-migration/src/dialog/tdeConfiguration/tdeMigrationDialog.ts +++ b/extensions/sql-migration/src/dialog/tdeConfiguration/tdeMigrationDialog.ts @@ -6,7 +6,8 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import * as constants from '../../constants/strings'; -import { logError, TelemetryErrorName, TelemetryViews } from '../../telemetry'; +import * as utils from '../../api/utils'; +import { logError, TelemetryAction, TelemetryViews, sendSqlMigrationActionEvent, getTelemetryProps } from '../../telemetry'; import { EOL } from 'os'; import { MigrationStateModel, OperationResult } from '../../models/stateMachine'; import { IconPathHelper } from '../../constants/iconPathHelper'; @@ -317,12 +318,28 @@ export class TdeMigrationDialog { }; this._model.tdeMigrationConfig.setTdeMigrationResult(this._tdeMigrationResult); // Set value on success. + + sendSqlMigrationActionEvent( + TelemetryViews.TdeMigrationDialog, + TelemetryAction.TdeMigrationSuccess, + { + ...getTelemetryProps(this._model) + }, + {} + ); } else { this._dialog!.okButton.enabled = false; - const errorDetails = operationResult.errors.join(EOL); - logError(TelemetryViews.MigrationLocalStorage, TelemetryErrorName.StartMigrationFailed, errorDetails); + sendSqlMigrationActionEvent( + TelemetryViews.TdeMigrationDialog, + TelemetryAction.TdeMigrationFailures, + { + ...getTelemetryProps(this._model), + 'runningAsAdmin': (await utils.isAdmin()).toString() + }, + {} + ); } this._startMigrationLoader.loading = false; @@ -339,6 +356,8 @@ export class TdeMigrationDialog { this._copyButton.enabled = false; this._dialog!.okButton.enabled = false; this._progressReportText.value = ''; + + logError(TelemetryViews.TdeMigrationDialog, TelemetryAction.TdeMigrationClientException, error); } this._headingText.value = constants.TDE_MIGRATE_RESULTS_HEADING_COMPLETED; @@ -433,13 +452,17 @@ export class TdeMigrationDialog { } } - private async _updateTableResultRow(dbName: string, succeeded: boolean, message: string): Promise { + private async _updateTableResultRow(dbName: string, succeeded: boolean, message: string, statusCode: string): Promise { if (!this._dbRowsMap.has(dbName)) { return; //Table not found } this._updateValidationResultRow(dbName, succeeded, message); + if (!succeeded) { + logError(TelemetryViews.TdeMigrationDialog, statusCode, {}); + } + // Update the table await this._updateTableData(); @@ -522,7 +545,4 @@ export class TdeMigrationDialog { return IconPathHelper.notStartedMigration; } } - - - } diff --git a/extensions/sql-migration/src/models/stateMachine.ts b/extensions/sql-migration/src/models/stateMachine.ts index 29d5f2e59c..8c8422bfae 100644 --- a/extensions/sql-migration/src/models/stateMachine.ts +++ b/extensions/sql-migration/src/models/stateMachine.ts @@ -947,7 +947,7 @@ export class MigrationStateModel implements Model, vscode.Disposable { public async startTdeMigration( accessToken: string, - reportUpdate: (dbName: string, succeeded: boolean, message: string) => Promise): Promise> { + reportUpdate: (dbName: string, succeeded: boolean, message: string, statusCode: string) => Promise): Promise> { const tdeEnabledDatabases = this.tdeMigrationConfig.getTdeEnabledDatabases(); const connectionString = await getSourceConnectionString(); diff --git a/extensions/sql-migration/src/service/contracts.ts b/extensions/sql-migration/src/service/contracts.ts index a25931c75e..138ccd8dfc 100644 --- a/extensions/sql-migration/src/service/contracts.ts +++ b/extensions/sql-migration/src/service/contracts.ts @@ -500,7 +500,7 @@ export interface ISqlMigrationService { targetManagedInstanceName: string, networkSharePath: string, accessToken: string, - reportUpdate: (dbName: string, succeeded: boolean, message: string) => void): Promise; + reportUpdate: (dbName: string, succeeded: boolean, message: string, statusCode: string) => void): Promise; } export interface TdeMigrationRequest { @@ -547,4 +547,5 @@ export interface TdeMigrateProgressParams { name: string; success: boolean; message: string; + statusCode: string; } diff --git a/extensions/sql-migration/src/service/features.ts b/extensions/sql-migration/src/service/features.ts index ca18937d39..f4d6f846f5 100644 --- a/extensions/sql-migration/src/service/features.ts +++ b/extensions/sql-migration/src/service/features.ts @@ -26,7 +26,7 @@ export abstract class MigrationExtensionService extends SqlOpsFeature } export class SqlMigrationService extends MigrationExtensionService implements contracts.ISqlMigrationService { - private _reportUpdate: ((dbName: string, succeeded: boolean, error: string) => void) | undefined = undefined; + private _reportUpdate: ((dbName: string, succeeded: boolean, error: string, statusCode: string) => void) | undefined = undefined; override providerId = ApiType.SqlMigrationProvider; @@ -58,7 +58,7 @@ export class SqlMigrationService extends MigrationExtensionService implements co if (this._reportUpdate === undefined) { return; } - this._reportUpdate(e.name, e.success, e.message); + this._reportUpdate(e.name, e.success, e.message, e.statusCode ?? ''); }); } @@ -288,7 +288,7 @@ export class SqlMigrationService extends MigrationExtensionService implements co targetManagedInstanceName: string, networkSharePath: string, accessToken: string, - reportUpdate: (dbName: string, succeeded: boolean, message: string) => void): Promise { + reportUpdate: (dbName: string, succeeded: boolean, message: string, statusCode: string) => void): Promise { this._reportUpdate = reportUpdate; let params: contracts.TdeMigrationParams = { @@ -306,7 +306,7 @@ export class SqlMigrationService extends MigrationExtensionService implements co try { // This call needs to be awaited so, the updates are sent during the execution of the task. - // If the task is not await, the finally block will execute and no update will be sent. + // If the task is not awaited, the finally block will execute and no updates will be sent. const result = await this._client.sendRequest(contracts.TdeMigrateRequest.type, params); return result; } diff --git a/extensions/sql-migration/src/telemetry.ts b/extensions/sql-migration/src/telemetry.ts index 6b8e8a96e1..1e24252a21 100644 --- a/extensions/sql-migration/src/telemetry.ts +++ b/extensions/sql-migration/src/telemetry.ts @@ -44,6 +44,7 @@ export enum TelemetryViews { LoginMigrationSelectorPage = 'LoginMigrationSelectorPage', LoginMigrationStatusPage = 'LoginMigrationStatusPage', TdeConfigurationDialog = 'TdeConfigurationDialog', + TdeMigrationDialog = 'TdeMigrationDialog', ValidIrDialog = 'validIrDialog', } @@ -76,10 +77,11 @@ export enum TelemetryAction { OpenLoginMigrationWizard = 'OpenLoginMigrationWizard', LoginMigrationStarted = 'LoginMigrationStarted', LoginMigrationCompleted = 'LoginMigrationCompleted', -} - -export enum TelemetryErrorName { - StartMigrationFailed = 'StartMigrationFailed' + TdeMigrationSuccess = 'TdeMigrationSuccess', + TdeMigrationFailures = 'TdeMigrationFailures', + TdeMigrationClientException = 'TdeMigrationClientException', + TdeConfigurationUseADS = 'TdeConfigurationUseADS', + TdeConfigurationIgnoreADS = 'TdeConfigurationIgnoreADS' } export function logError(telemetryView: TelemetryViews, err: string, error: any): void { diff --git a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts index 8650b3cc51..93b96f6294 100644 --- a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts +++ b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts @@ -18,10 +18,9 @@ import { IconPath, IconPathHelper } from '../constants/iconPathHelper'; import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController'; import * as styles from '../constants/styles'; import { SkuEditParametersDialog } from '../dialog/skuRecommendationResults/skuEditParametersDialog'; -import { logError, TelemetryViews } from '../telemetry'; +import { logError, TelemetryViews, TelemetryAction, sendSqlMigrationActionEvent, getTelemetryProps } from '../telemetry'; import { TdeConfigurationDialog } from '../dialog/tdeConfiguration/tdeConfigurationDialog'; import { TdeMigrationModel } from '../models/tdeModels'; -import * as os from 'os'; import { getSourceConnectionProfile } from '../api/sqlUtils'; export interface Product { @@ -817,7 +816,7 @@ export class SKURecommendationPage extends MigrationWizardPage { if (this._matchWithEncryptedDatabases(encryptedDbFound)) { this.migrationStateModel.tdeMigrationConfig = this._previousMiTdeMigrationConfig; } else { - if (os.platform() !== 'win32') //Only available for windows for now. + if (!utils.isWindows()) //Only available for windows for now. return; //Set encrypted databases @@ -843,6 +842,17 @@ export class SKURecommendationPage extends MigrationWizardPage { const tdeMsg = (this.migrationStateModel.tdeMigrationConfig.isTdeMigrationMethodAdsConfirmed()) ? constants.TDE_WIZARD_MSG_TDE : constants.TDE_WIZARD_MSG_MANUAL; this._tdedatabaseSelectedHelperText.value = constants.TDE_MSG_DATABASES_SELECTED(this.migrationStateModel.tdeMigrationConfig.getTdeEnabledDatabasesCount(), tdeMsg); + const tdeTelemetryAction = (this.migrationStateModel.tdeMigrationConfig.isTdeMigrationMethodAdsConfirmed()) ? TelemetryAction.TdeConfigurationUseADS : TelemetryAction.TdeConfigurationIgnoreADS; + + sendSqlMigrationActionEvent( + TelemetryViews.TdeConfigurationDialog, + tdeTelemetryAction, + { + ...getTelemetryProps(this.migrationStateModel) + }, + {} + ); + return this._tdeEditButton.focus(); }