Send telemetry with Auth Library when adding/refreshing account (#22506)

This commit is contained in:
Cheena Malhotra
2023-03-29 11:56:56 -07:00
committed by GitHub
parent 87fb3f9b86
commit e70865ff20
3 changed files with 77 additions and 10 deletions

View File

@@ -43,6 +43,7 @@ export const enum TelemetryView {
ExecutionPlan = 'ExecutionPlan', ExecutionPlan = 'ExecutionPlan',
ExtensionHost = 'ExtensionHost', ExtensionHost = 'ExtensionHost',
ExtensionRecommendationDialog = 'ExtensionRecommendationDialog', ExtensionRecommendationDialog = 'ExtensionRecommendationDialog',
LinkedAccounts = 'LinkedAccounts',
Notebook = 'Notebook', Notebook = 'Notebook',
NotifyEncryptionDialog = 'NotifyEncryptionDialog', NotifyEncryptionDialog = 'NotifyEncryptionDialog',
ResultsPanel = 'ResultsPanel', ResultsPanel = 'ResultsPanel',
@@ -53,13 +54,18 @@ export const enum TelemetryView {
export const enum TelemetryError { export const enum TelemetryError {
DatabaseConnectionError = 'DatabaseConnectionError', DatabaseConnectionError = 'DatabaseConnectionError',
ObjectExplorerExpandError = 'ObjectExplorerExpandError' ObjectExplorerExpandError = 'ObjectExplorerExpandError',
AddAzureAccountError = 'AddAzureAccountError',
AddAzureAccountErrorNoResult = 'AddAzureAccountErrorNoResult',
RefreshAzureAccountError = 'RefreshAzureAccountError',
RefreshAzureAccountErrorNoResult = 'RefreshAzureAccountErrorNoResult',
} }
export const enum TelemetryAction { export const enum TelemetryAction {
adsCommandExecuted = 'adsCommandExecuted', adsCommandExecuted = 'adsCommandExecuted',
AddExecutionPlan = 'AddExecutionPlan', AddExecutionPlan = 'AddExecutionPlan',
AddServerGroup = 'AddServerGroup', AddServerGroup = 'AddServerGroup',
AddAzureAccount = 'AddAzureAccount',
BackupCreated = 'BackupCreated', BackupCreated = 'BackupCreated',
ConnectToServer = 'ConnectToServer', ConnectToServer = 'ConnectToServer',
CustomZoom = 'CustomZoom', CustomZoom = 'CustomZoom',
@@ -95,6 +101,7 @@ export const enum TelemetryAction {
OpenQuery = 'OpenQuery', OpenQuery = 'OpenQuery',
OpenExecutionPlanProperties = 'OpenExecutionPlanProperties', OpenExecutionPlanProperties = 'OpenExecutionPlanProperties',
PublishChanges = 'PublishChanges', PublishChanges = 'PublishChanges',
RefreshAzureAccount = 'RefreshAzureAccount',
RestoreRequested = 'RestoreRequested', RestoreRequested = 'RestoreRequested',
RunAgentJob = 'RunAgentJob', RunAgentJob = 'RunAgentJob',
RunQuery = 'RunQuery', RunQuery = 'RunQuery',
@@ -133,6 +140,7 @@ export const enum NbTelemetryAction {
export const enum TelemetryPropertyName { export const enum TelemetryPropertyName {
ChartMaxRowCountExceeded = 'chartMaxRowCountExceeded', ChartMaxRowCountExceeded = 'chartMaxRowCountExceeded',
ConnectionSource = 'connectionSource' ConnectionSource = 'connectionSource',
AuthLibrary = 'AuthLibrary'
} }

View File

@@ -28,6 +28,8 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { filterAccounts } from 'sql/workbench/services/accountManagement/browser/accountDialog'; import { filterAccounts } from 'sql/workbench/services/accountManagement/browser/accountDialog';
import { ADAL_AUTH_LIBRARY, MSAL_AUTH_LIBRARY, AuthLibrary, AZURE_AUTH_LIBRARY_CONFIG, getAuthLibrary } from 'sql/workbench/services/accountManagement/utils'; import { ADAL_AUTH_LIBRARY, MSAL_AUTH_LIBRARY, AuthLibrary, AZURE_AUTH_LIBRARY_CONFIG, getAuthLibrary } from 'sql/workbench/services/accountManagement/utils';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import { TelemetryAction, TelemetryError, TelemetryPropertyName, TelemetryView } from 'sql/platform/telemetry/common/telemetryKeys';
export class AccountManagementService implements IAccountManagementService { export class AccountManagementService implements IAccountManagementService {
// CONSTANTS /////////////////////////////////////////////////////////// // CONSTANTS ///////////////////////////////////////////////////////////
@@ -61,7 +63,8 @@ export class AccountManagementService implements IAccountManagementService {
@IOpenerService private _openerService: IOpenerService, @IOpenerService private _openerService: IOpenerService,
@ILogService private readonly _logService: ILogService, @ILogService private readonly _logService: ILogService,
@INotificationService private readonly _notificationService: INotificationService, @INotificationService private readonly _notificationService: INotificationService,
@IConfigurationService private _configurationService: IConfigurationService @IConfigurationService private _configurationService: IConfigurationService,
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService
) { ) {
this._mementoContext = new Memento(AccountManagementService.ACCOUNT_MEMENTO, this._storageService); this._mementoContext = new Memento(AccountManagementService.ACCOUNT_MEMENTO, this._storageService);
const mementoObj = this._mementoContext.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE); const mementoObj = this._mementoContext.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE);
@@ -147,18 +150,28 @@ export class AccountManagementService implements IAccountManagementService {
if (accountResult.canceled === true) { if (accountResult.canceled === true) {
return; return;
} else { } else {
this._telemetryService.createErrorEvent(TelemetryView.LinkedAccounts, TelemetryError.AddAzureAccountError, accountResult.errorCode,
this.getErrorType(accountResult.errorMessage))
.withAdditionalProperties({
[TelemetryPropertyName.AuthLibrary]: this._authLibrary
})
.send();
if (accountResult.errorCode && accountResult.errorMessage) { if (accountResult.errorCode && accountResult.errorMessage) {
throw new Error(localize('addAccountFailedCodeMessage', `{0} \nError Message: {1}`, accountResult.errorCode, accountResult.errorMessage)); throw new Error(localize('addAccountFailedCodeMessage', `{0} \nError Message: {1}`, accountResult.errorCode, accountResult.errorMessage));
} else if (accountResult.errorMessage) {
throw new Error(localize('addAccountFailedMessage', `{0}`, accountResult.errorMessage));
} else { } else {
throw new Error(genericAccountErrorMessage); throw new Error(accountResult.errorMessage ?? genericAccountErrorMessage);
} }
} }
} }
let result = await this._accountStore.addOrUpdate(accountResult); let result = await this._accountStore.addOrUpdate(accountResult);
if (!result) { if (!result) {
this._logService.error('adding account failed'); this._logService.error('Adding account failed, no result received.');
this._telemetryService.createErrorEvent(TelemetryView.LinkedAccounts, TelemetryError.AddAzureAccountErrorNoResult, '-1',
this.getErrorType())
.withAdditionalProperties({
[TelemetryPropertyName.AuthLibrary]: this._authLibrary
})
.send();
throw new Error(genericAccountErrorMessage); throw new Error(genericAccountErrorMessage);
} }
if (result.accountAdded) { if (result.accountAdded) {
@@ -168,6 +181,11 @@ export class AccountManagementService implements IAccountManagementService {
if (result.accountModified) { if (result.accountModified) {
this.spliceModifiedAccount(provider, result.changedAccount); this.spliceModifiedAccount(provider, result.changedAccount);
} }
this._telemetryService.createActionEvent(TelemetryView.LinkedAccounts, TelemetryAction.AddAzureAccount)
.withAdditionalProperties({
[TelemetryPropertyName.AuthLibrary]: this._authLibrary
})
.send();
this.fireAccountListUpdate(provider, result.accountAdded); this.fireAccountListUpdate(provider, result.accountAdded);
} finally { } finally {
notificationHandler.close(); notificationHandler.close();
@@ -207,6 +225,7 @@ export class AccountManagementService implements IAccountManagementService {
* @return Promise to return an account * @return Promise to return an account
*/ */
public refreshAccount(account: azdata.Account): Promise<azdata.Account> { public refreshAccount(account: azdata.Account): Promise<azdata.Account> {
const genericAccountErrorMessage = localize('refreshAccountFailedGenericMessage', 'Refreshing account failed, check Azure Accounts log for more info.')
return this.doWithProvider(account.key.providerId, async (provider) => { return this.doWithProvider(account.key.providerId, async (provider) => {
let refreshedAccount = await provider.provider.refresh(account); let refreshedAccount = await provider.provider.refresh(account);
if (!this.isAccountResult(refreshedAccount)) { if (!this.isAccountResult(refreshedAccount)) {
@@ -214,13 +233,33 @@ export class AccountManagementService implements IAccountManagementService {
// Pattern here is to throw if this fails. Handled upstream. // Pattern here is to throw if this fails. Handled upstream.
throw new Error(localize('refreshCanceled', "Refresh account was canceled by the user")); throw new Error(localize('refreshCanceled', "Refresh account was canceled by the user"));
} else { } else {
throw new Error(localize('refreshFailed', `${0} \nError Message: ${1}`, refreshedAccount.errorCode, refreshedAccount.errorMessage)); this._telemetryService.createErrorEvent(TelemetryView.LinkedAccounts, TelemetryError.RefreshAzureAccountError, refreshedAccount.errorCode,
this.getErrorType(refreshedAccount.errorMessage))
.withAdditionalProperties({
[TelemetryPropertyName.AuthLibrary]: this._authLibrary
})
.send();
if (refreshedAccount.errorCode && refreshedAccount.errorMessage) {
throw new Error(localize('refreshFailed', `{0} \nError Message: {1}`, refreshedAccount.errorCode, refreshedAccount.errorMessage));
} else {
throw new Error(refreshedAccount.errorMessage ?? genericAccountErrorMessage);
}
} }
} else { } else {
account = refreshedAccount; account = refreshedAccount;
} }
let result = await this._accountStore.addOrUpdate(account); let result = await this._accountStore.addOrUpdate(account);
if (!result) {
this._logService.error('Refreshing account failed, no result received.');
this._telemetryService.createErrorEvent(TelemetryView.LinkedAccounts, TelemetryError.RefreshAzureAccountErrorNoResult, '-1',
this.getErrorType())
.withAdditionalProperties({
[TelemetryPropertyName.AuthLibrary]: this._authLibrary
})
.send();
throw new Error(genericAccountErrorMessage);
}
if (result.accountAdded) { if (result.accountAdded) {
// Double check that there isn't a matching account // Double check that there isn't a matching account
let indexToRemove = this.findAccountIndex(provider.accounts, result.changedAccount); let indexToRemove = this.findAccountIndex(provider.accounts, result.changedAccount);
@@ -241,11 +280,30 @@ export class AccountManagementService implements IAccountManagementService {
} }
} }
this._telemetryService.createActionEvent(TelemetryView.LinkedAccounts, TelemetryAction.RefreshAzureAccount)
.withAdditionalProperties({
[TelemetryPropertyName.AuthLibrary]: this._authLibrary
})
.send();
this.fireAccountListUpdate(provider, result.accountAdded); this.fireAccountListUpdate(provider, result.accountAdded);
return result.changedAccount!; return result.changedAccount!;
}); });
} }
private getErrorType(errorMessage?: string | undefined): string {
let errorType: string = 'Unknown';
if (errorMessage) {
if (errorMessage.toLocaleLowerCase().includes('token')) {
errorType = 'AccessToken';
} else if (errorMessage.toLocaleLowerCase().includes('timeout')) {
errorType = 'Timeout';
} else if (errorMessage.toLocaleLowerCase().includes('cache')) {
errorType = 'TokenCache'
}
}
return errorType;
}
/** /**
* Retrieves metadata of all providers that have been registered * Retrieves metadata of all providers that have been registered
* @returns Registered account providers * @returns Registered account providers

View File

@@ -19,6 +19,7 @@ import { TestNotificationService } from 'vs/platform/notification/test/common/te
import { AccountDialog } from 'sql/workbench/services/accountManagement/browser/accountDialog'; import { AccountDialog } from 'sql/workbench/services/accountManagement/browser/accountDialog';
import { Emitter } from 'vs/base/common/event'; import { Emitter } from 'vs/base/common/event';
import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService'; import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService';
import { NullAdsTelemetryService } from 'sql/platform/telemetry/common/adsTelemetryService';
// SUITE CONSTANTS ///////////////////////////////////////////////////////// // SUITE CONSTANTS /////////////////////////////////////////////////////////
const hasAccountProvider: azdata.AccountProviderMetadata = { const hasAccountProvider: azdata.AccountProviderMetadata = {
@@ -538,10 +539,11 @@ function getTestState(): AccountManagementState {
const testNotificationService = new TestNotificationService(); const testNotificationService = new TestNotificationService();
const testConfigurationService = new TestConfigurationService(); const testConfigurationService = new TestConfigurationService();
const mockTelemetryService = new NullAdsTelemetryService();
// Create the account management service // Create the account management service
let ams = new AccountManagementService(mockInstantiationService.object, new TestStorageService(), let ams = new AccountManagementService(mockInstantiationService.object, new TestStorageService(),
undefined, undefined, undefined, testNotificationService, testConfigurationService); undefined, undefined, undefined, testNotificationService, testConfigurationService, mockTelemetryService);
// Wire up event handlers // Wire up event handlers
let evUpdate = new EventVerifierSingle<UpdateAccountListEventParams>(); let evUpdate = new EventVerifierSingle<UpdateAccountListEventParams>();
@@ -567,7 +569,6 @@ function getMockAccountProvider(): TypeMoq.Mock<azdata.AccountProvider> {
mockProvider.setup(x => x.clear(TypeMoq.It.isAny())).returns(() => Promise.resolve()); mockProvider.setup(x => x.clear(TypeMoq.It.isAny())).returns(() => Promise.resolve());
mockProvider.setup(x => x.initialize(TypeMoq.It.isAny())).returns(param => Promise.resolve(param)); mockProvider.setup(x => x.initialize(TypeMoq.It.isAny())).returns(param => Promise.resolve(param));
mockProvider.setup(x => x.prompt()).returns(() => Promise.resolve(account)); mockProvider.setup(x => x.prompt()).returns(() => Promise.resolve(account));
return mockProvider; return mockProvider;
} }