From 0503c8d8fe877be7875c59a9536a85ba62e9a1dd Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Sun, 30 Jun 2019 19:38:04 +0000 Subject: [PATCH] Initial work to update telemetry to use Common Schema (#6203) * Update admin-tool-ext-win to use new ads-extension-telemetry package * Add AdsTelemetryService for sending telemetry events using the ADS Common Schema * Clean up unused import and add engineType * Address PR comments * Update private var names --- extensions/admin-tool-ext-win/package.json | 2 +- extensions/admin-tool-ext-win/src/main.ts | 41 ++-- .../admin-tool-ext-win/src/telemetry.ts | 94 +-------- extensions/admin-tool-ext-win/yarn.lock | 24 ++- .../platform/telemetry/adsTelemetryService.ts | 189 ++++++++++++++++++ .../telemetry/telemetry.contribution.ts | 21 +- src/sql/platform/telemetry/telemetry.ts | 87 ++++++++ src/sql/platform/telemetry/telemetryKeys.ts | 4 + src/vs/workbench/browser/workbench.ts | 2 + src/vs/workbench/workbench.main.ts | 3 + 10 files changed, 344 insertions(+), 123 deletions(-) create mode 100644 src/sql/platform/telemetry/adsTelemetryService.ts create mode 100644 src/sql/platform/telemetry/telemetry.ts diff --git a/extensions/admin-tool-ext-win/package.json b/extensions/admin-tool-ext-win/package.json index 96d0f46b97..c081eaabe1 100644 --- a/extensions/admin-tool-ext-win/package.json +++ b/extensions/admin-tool-ext-win/package.json @@ -70,7 +70,7 @@ ] }, "dependencies": { - "vscode-extension-telemetry": "^0.0.15", + "ads-extension-telemetry": "github:Charles-Gagnon/ads-extension-telemetry#0.1.0", "vscode-nls": "^3.2.1" }, "devDependencies": { diff --git a/extensions/admin-tool-ext-win/src/main.ts b/extensions/admin-tool-ext-win/src/main.ts index e6d451c31d..d8e518f1ef 100644 --- a/extensions/admin-tool-ext-win/src/main.ts +++ b/extensions/admin-tool-ext-win/src/main.ts @@ -7,7 +7,7 @@ import * as nls from 'vscode-nls'; import * as path from 'path'; import * as azdata from 'azdata'; import * as vscode from 'vscode'; -import { Telemetry } from './telemetry'; +import { TelemetryReporter, TelemetryViews } from './telemetry'; import { doubleEscapeSingleQuotes, backEscapeDoubleQuotes, getTelemetryErrorType } from './utils'; import { ChildProcess, exec } from 'child_process'; const localize = nls.loadMessageBundle(); @@ -95,7 +95,7 @@ function registerCommands(context: vscode.ExtensionContext): void { */ async function handleLaunchSsmsMinPropertiesDialogCommand(connectionContext?: azdata.ObjectExplorerContext): Promise { if (!connectionContext) { - Telemetry.sendTelemetryEventForError('NoConnectionContext', { action: 'Properties' }); + TelemetryReporter.sendErrorEvent(TelemetryViews.SsmsMinProperties, 'NoConnectionContext'); vscode.window.showErrorMessage(localize('adminToolExtWin.noConnectionContextForProp', 'No ConnectionContext provided for handleLaunchSsmsMinPropertiesDialogCommand')); return; } @@ -107,7 +107,7 @@ async function handleLaunchSsmsMinPropertiesDialogCommand(connectionContext?: az else if (connectionContext.nodeInfo) { nodeType = connectionContext.nodeInfo.nodeType; } else { - Telemetry.sendTelemetryEventForError('NoOENode', { action: 'Properties' }); + TelemetryReporter.sendErrorEvent(TelemetryViews.SsmsMinProperties, 'NoOENode'); vscode.window.showErrorMessage(localize('adminToolExtWin.noOENode', 'Could not determine Object Explorer node from connectionContext : {0}', JSON.stringify(connectionContext))); return; } @@ -124,7 +124,7 @@ async function handleLaunchSsmsMinPropertiesDialogCommand(connectionContext?: az async function handleLaunchSsmsMinGswDialogCommand(connectionContext?: azdata.ObjectExplorerContext): Promise { const action = 'GenerateScripts'; if (!connectionContext) { - Telemetry.sendTelemetryEventForError('NoConnectionContext', { action: action }); + TelemetryReporter.sendErrorEvent(TelemetryViews.SsmsMinGsw, 'NoConnectionContext'); vscode.window.showErrorMessage(localize('adminToolExtWin.noConnectionContextForGsw', 'No ConnectionContext provided for handleLaunchSsmsMinPropertiesDialogCommand')); } @@ -141,7 +141,7 @@ async function handleLaunchSsmsMinGswDialogCommand(connectionContext?: azdata.Ob */ async function launchSsmsDialog(action: string, connectionContext: azdata.ObjectExplorerContext): Promise { if (!connectionContext.connectionProfile) { - Telemetry.sendTelemetryEventForError('NoConnectionProfile', { action: action }); + TelemetryReporter.sendErrorEvent(TelemetryViews.SsmsMinDialog, 'NoConnectionProfile'); vscode.window.showErrorMessage(localize('adminToolExtWin.noConnectionProfile', 'No connectionProfile provided from connectionContext : {0}', JSON.stringify(connectionContext))); return; } @@ -155,7 +155,7 @@ async function launchSsmsDialog(action: string, connectionContext: azdata.Object oeNode = await azdata.objectexplorer.getNode(connectionContext.connectionProfile.id, connectionContext.nodeInfo.nodePath); } else { - Telemetry.sendTelemetryEventForError('NoOENode', { action: action }); + TelemetryReporter.sendErrorEvent(TelemetryViews.SsmsMinDialog, 'NoOENode'); vscode.window.showErrorMessage(localize('adminToolExtWin.noOENode', 'Could not determine Object Explorer node from connectionContext : {0}', JSON.stringify(connectionContext))); return; } @@ -178,13 +178,15 @@ async function launchSsmsDialog(action: string, connectionContext: azdata.Object }; const args = buildSsmsMinCommandArgs(params); - - Telemetry.sendTelemetryEvent('LaunchSsmsDialog', - { - action: action, - nodeType: oeNode ? oeNode.nodeType : 'Server', - authType: connectionContext.connectionProfile.authenticationType - }); + TelemetryReporter.createActionEvent( + TelemetryViews.SsmsMinDialog, + 'LaunchSsmsDialog', + '', + action).withAdditionalProperties( + { + nodeType: oeNode ? oeNode.nodeType : 'Server' + }).withConnectionInfo(connectionContext.connectionProfile) + .send(); vscode.window.setStatusBarMessage(localize('adminToolExtWin.launchingDialogStatus', 'Launching dialog...'), 3000); @@ -196,11 +198,14 @@ async function launchSsmsDialog(action: string, connectionContext: azdata.Object // Process has exited so remove from map of running processes runningProcesses.delete(proc.pid); const err = stderr.toString(); - Telemetry.sendTelemetryEvent('LaunchSsmsDialogResult', { - action: params.action, - returnCode: execException && execException.code ? execException.code.toString() : '0', - errorType: getTelemetryErrorType(err) - }); + if ((execException && execException.code !== 0) || err !== '') { + TelemetryReporter.sendErrorEvent( + TelemetryViews.SsmsMinDialog, + 'LaunchSsmsDialogError', + execException ? execException.code.toString() : '', + getTelemetryErrorType(err)); + } + if (err !== '') { vscode.window.showErrorMessage(localize( 'adminToolExtWin.ssmsMinError', diff --git a/extensions/admin-tool-ext-win/src/telemetry.ts b/extensions/admin-tool-ext-win/src/telemetry.ts index 530c8a8dd3..02e8ec32b2 100644 --- a/extensions/admin-tool-ext-win/src/telemetry.ts +++ b/extensions/admin-tool-ext-win/src/telemetry.ts @@ -4,99 +4,23 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import * as vscode from 'vscode'; -import TelemetryReporter from 'vscode-extension-telemetry'; +import AdsTelemetryReporter from 'ads-extension-telemetry'; import * as Utils from './utils'; const packageJson = require('../package.json'); -export interface ITelemetryEventProperties { - [key: string]: string; +let packageInfo = Utils.getPackageInfo(packageJson); + +export const TelemetryReporter = new AdsTelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey); + +export enum TelemetryViews { + SsmsMinProperties = 'SsmsMinProperties', + SsmsMinGsw = 'SsmsMinGsw', + SsmsMinDialog = 'SsmsMinDialog' } -export interface ITelemetryEventMeasures { - [key: string]: number; -} -/** - * Filters error paths to only include source files. Exported to support testing - */ -export function filterErrorPath(line: string): string { - if (line) { - let values: string[] = line.split('/out/'); - if (values.length <= 1) { - // Didn't match expected format - return line; - } else { - return values[1]; - } - } -} -export class Telemetry { - private static reporter: TelemetryReporter; - private static disabled: boolean; - /** - * Disable telemetry reporting - */ - public static disable(): void { - this.disabled = true; - } - /** - * Initialize the telemetry reporter for use. - */ - public static initialize(): void { - if (typeof this.reporter === 'undefined') { - // Check if the user has opted out of telemetry - if (!vscode.workspace.getConfiguration('telemetry').get('enableTelemetry', true)) { - this.disable(); - return; - } - - let packageInfo = Utils.getPackageInfo(packageJson); - this.reporter = new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey); - } - } - - /** - * Send a telemetry event for a general error - * @param err The error to log - */ - public static sendTelemetryEventForError(err: string, properties?: ITelemetryEventProperties): void { - this.sendTelemetryEvent('Error', { error: err, ...properties }); - } - - /** - * Send a telemetry event using application insights - */ - public static sendTelemetryEvent( - eventName: string, - properties?: ITelemetryEventProperties, - measures?: ITelemetryEventMeasures): void { - - if (typeof this.disabled === 'undefined') { - this.disabled = false; - } - - if (this.disabled || typeof (this.reporter) === 'undefined') { - // Don't do anything if telemetry is disabled - return; - } - - if (!properties || typeof properties === 'undefined') { - properties = {}; - } - - try { - this.reporter.sendTelemetryEvent(eventName, properties, measures); - } catch (telemetryErr) { - // If sending telemetry event fails ignore it so it won't break the extension - console.error('Failed to send telemetry event. error: ' + telemetryErr); - } - } -} - -Telemetry.initialize(); diff --git a/extensions/admin-tool-ext-win/yarn.lock b/extensions/admin-tool-ext-win/yarn.lock index 2f11d69ee1..c4a48d4634 100644 --- a/extensions/admin-tool-ext-win/yarn.lock +++ b/extensions/admin-tool-ext-win/yarn.lock @@ -2,6 +2,12 @@ # yarn lockfile v1 +"ads-extension-telemetry@github:Charles-Gagnon/ads-extension-telemetry#0.1.0": + version "0.1.0" + resolved "https://codeload.github.com/Charles-Gagnon/ads-extension-telemetry/tar.gz/70c2fea10e9ff6e329c4c5ec0b77017ada514b6d" + dependencies: + vscode-extension-telemetry "0.1.1" + ajv@^6.5.5: version "6.9.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.2.tgz#4927adb83e7f48e5a32b45729744c71ec39c9c7b" @@ -49,10 +55,10 @@ ansi-wrap@0.1.0: resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768= -applicationinsights@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927" - integrity sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc= +applicationinsights@1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5" + integrity sha512-KzOOGdphOS/lXWMFZe5440LUdFbrLpMvh2SaRxn7BmiI550KAoSb2gIhiq6kJZ9Ir3AxRRztjhzif+e5P5IXIg== dependencies: diagnostic-channel "0.2.0" diagnostic-channel-publishers "0.2.1" @@ -2271,12 +2277,12 @@ vinyl@~2.0.1: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vscode-extension-telemetry@^0.0.15: - version "0.0.15" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.15.tgz#685c32f3b67e8fb85ba689c1d7f88ff90ff87856" - integrity sha512-Yf6dL9r2x2GISI1xh22XsAaydSTQG/4aBitu8sGBwGr42n2TyOsIXGtXSDgqQBNZgYD6+P1EHqrrzetn9ekWTQ== +vscode-extension-telemetry@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.1.tgz#91387e06b33400c57abd48979b0e790415ae110b" + integrity sha512-TkKKG/B/J94DP5qf6xWB4YaqlhWDg6zbbqVx7Bz//stLQNnfE9XS1xm3f6fl24c5+bnEK0/wHgMgZYKIKxPeUA== dependencies: - applicationinsights "1.0.1" + applicationinsights "1.0.8" vscode-nls@^3.2.1: version "3.2.5" diff --git a/src/sql/platform/telemetry/adsTelemetryService.ts b/src/sql/platform/telemetry/adsTelemetryService.ts new file mode 100644 index 0000000000..5c71405800 --- /dev/null +++ b/src/sql/platform/telemetry/adsTelemetryService.ts @@ -0,0 +1,189 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IAdsTelemetryService, ITelemetryInfo, ITelemetryEvent, ITelemetryConnectionInfo, ITelemetryEventMeasures, ITelemetryEventProperties } from 'sql/platform/telemetry/telemetry'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + + +class TelemetryEventImpl implements ITelemetryEvent { + constructor( + private _telemetryService: ITelemetryService, + private _logService: ILogService, + private _eventName: string, + private _properties?: ITelemetryEventProperties, + private _measurements?: ITelemetryEventMeasures) { + _properties = _properties || {}; + _measurements = _measurements || {}; + } + + public send(): void { + try { + this._telemetryService.publicLog(this._eventName, { properties: this._properties, measurements: this._measurements }); + } + catch (e) { + // We don't want exceptions sending telemetry to break functionality so just log and ignore + if (this._logService) { + const msg = e instanceof Error ? e.message : e; + this._logService.warn(`Error sending ${this._eventName} event ${msg}`); + } + } + } + + public withAdditionalProperties(additionalProperties: ITelemetryEventProperties): ITelemetryEvent { + Object.assign(this._properties, additionalProperties); + return this; + } + + public withAdditionalMeasurements(additionalMeasurements: ITelemetryEventMeasures): ITelemetryEvent { + Object.assign(this._measurements, additionalMeasurements); + return this; + } + + public withConnectionInfo(connectionInfo: ITelemetryConnectionInfo): ITelemetryEvent { + Object.assign(this._properties, + { + authenticationType: connectionInfo.authenticationType, + providerName: connectionInfo.providerName, + serverType: connectionInfo.serverType, + engineType: connectionInfo.engineType + }); + return this; + } +} + +export class AdsTelemetryService implements IAdsTelemetryService { + + _serviceBrand: any; + + constructor( + @ITelemetryService private telemetryService: ITelemetryService, + @ILogService private logService: ILogService + ) { } + + setEnabled(value: boolean): void { + return this.telemetryService.setEnabled(value); + } + + get isOptedIn(): boolean { + return this.telemetryService.isOptedIn; + } + + getTelemetryInfo(): Promise { + return this.telemetryService.getTelemetryInfo(); + } + + /** + * Creates a View event that can be sent later. This is used to log that a particular page or item was seen. + * @param view The name of the page or item that was viewed + */ + public createViewEvent(view: string): ITelemetryEvent { + return new TelemetryEventImpl(this.telemetryService, this.logService, 'view', { + view: view + }); + } + + /** + * Sends a View event. This is used to log that a particular page or item was seen. + * @param view The name of the page or item that was viewed + */ + public sendViewEvent(view: string): void { + this.createViewEvent(view).send(); + } + + /** + * Creates an Action event that can be sent later. This is used to log when an action was taken, such as clicking a button. + * @param view The name of the page or item where this action occurred + * @param action The name of the action taken + * @param target The name of the item being acted on + * @param source The source of the action + */ + public createActionEvent(view: string, action: string, target: string = '', source: string = '', durationInMs?: number): ITelemetryEvent { + const measures: ITelemetryEventMeasures = durationInMs ? { durationInMs: durationInMs } : {}; + return new TelemetryEventImpl(this.telemetryService, this.logService, 'action', { + view: view, + action: action, + target: target, + source: source + }, measures); + } + + /** + * Sends a Action event. This is used to log when an action was taken, such as clicking a button. + * @param view The name of the page or item where this action occurred + * @param action The name of the action taken + * @param target The name of the item being acted on + * @param source The source of the action + */ + public sendActionEvent(view: string, action: string, target: string = '', source: string = '', durationInMs?: number): void { + this.createActionEvent(view, action, target, source, durationInMs).send(); + } + + /** + * Creates a Metrics event that can be sent later. This is used to log measurements taken. + * @param metrics The metrics to send + */ + public createMetricsEvent(metrics: ITelemetryEventMeasures, groupName: string = ''): ITelemetryEvent { + return new TelemetryEventImpl(this.telemetryService, this.logService, 'metrics', { groupName: groupName }, metrics); + } + + /** + * Sends a Metrics event. This is used to log measurements taken. + * @param measurements The metrics to send + */ + public sendMetricsEvent(metrics: ITelemetryEventMeasures, groupName: string = ''): void { + this.createMetricsEvent(metrics, groupName).send(); + } + + /** + * Creates a new Error event that can be sent later. This is used to log errors that occur. + * @param view The name of the page or item where the error occurred + * @param name The friendly name of the error + * @param errorCode The error code returned + * @param errorType The specific type of error + * @param properties Optional additional properties + */ + public createErrorEvent(view: string, name: string, errorCode: string = '', errorType: string = ''): ITelemetryEvent { + return new TelemetryEventImpl(this.telemetryService, this.logService, 'error', { + view: view, + name: name, + errorCode: errorCode, + errorType: errorType + }); + } + + /** + * Sends a Error event. This is used to log errors that occur. + * @param view The name of the page or item where the error occurred + * @param name The friendly name of the error + * @param errorCode The error code returned + * @param errorType The specific type of error + */ + public sendErrorEvent(view: string, name: string, errorCode: string = '', errorType: string = ''): void { + this.createErrorEvent(view, name, errorCode, errorType).send(); + } + + /** + * Creates a custom telemetry event with the specified name that can be sent later. Generally the other send functions should be + * preferred over this - only use this if you absolutely need a custom event that can't be covered by the other methods. + * @param eventName The name of the event. Will be prefixed with / + * @param properties The list of properties to send along with the event + * @param measurements The list of measurements to send along with the event + */ + public createTelemetryEvent(eventName: string, properties?: ITelemetryEventProperties, measurements?: ITelemetryEventMeasures): ITelemetryEvent { + return new TelemetryEventImpl(this.telemetryService, this.logService, eventName, properties, measurements); + } + + /** + * Sends a custom telemetry event with the specified name. Generally the other send functions should be + * preferred over this - only use this if you absolutely need a custom event that can't be covered by the other + * @param eventName The name of the event. Will be prefixed with / + * @param properties The list of properties to send along with the event + * @param measurements The list of measurements to send along with the event + */ + public sendTelemetryEvent(eventName: string, properties?: ITelemetryEventProperties, measurements?: ITelemetryEventMeasures): void { + this.createTelemetryEvent(eventName, properties, measurements).send(); + } +} \ No newline at end of file diff --git a/src/sql/platform/telemetry/telemetry.contribution.ts b/src/sql/platform/telemetry/telemetry.contribution.ts index 23ed4345e8..27d3d85333 100644 --- a/src/sql/platform/telemetry/telemetry.contribution.ts +++ b/src/sql/platform/telemetry/telemetry.contribution.ts @@ -3,10 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { TelemetryView } from 'sql/platform/telemetry/telemetryKeys'; +import { IAdsTelemetryService } from 'sql/platform/telemetry/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Disposable } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ICommandService, ICommandEvent } from 'vs/platform/commands/common/commands'; @@ -14,7 +15,7 @@ import { ICommandService, ICommandEvent } from 'vs/platform/commands/common/comm export class SqlTelemetryContribution extends Disposable implements IWorkbenchContribution { constructor( - @ITelemetryService private telemetryService: ITelemetryService, + @IAdsTelemetryService private telemetryService: IAdsTelemetryService, @IStorageService storageService: IStorageService, @ICommandService commandService: ICommandService ) { @@ -25,7 +26,7 @@ export class SqlTelemetryContribution extends Disposable implements IWorkbenchCo (e: ICommandEvent) => { // Filter out high-frequency events if (!['type', 'cursorUp', 'cursorDown', 'cursorRight', 'cursorLeft', 'deleteLeft', 'deleteRight'].find(id => id === e.commandId)) { - telemetryService.publicLog('adsCommandExecuted', { id: e.commandId }); + telemetryService.sendActionEvent(TelemetryView.Shell, 'adsCommandExecuted', e.commandId); } })); const dailyLastUseDate: number = Date.parse(storageService.get('telemetry.dailyLastUseDate', StorageScope.GLOBAL, '0')); @@ -38,14 +39,14 @@ export class SqlTelemetryContribution extends Disposable implements IWorkbenchCo // daily user event if (this.didDayChange(dailyLastUseDate)) { // daily first use - telemetryService.publicLog('telemetry.dailyFirstUse', { dailyFirstUse: true }); + telemetryService.sendTelemetryEvent('telemetry.dailyFirstUse', { dailyFirstUse: 'true' }); storageService.store('telemetry.dailyLastUseDate', todayString, StorageScope.GLOBAL); } // weekly user event if (this.didWeekChange(weeklyLastUseDate)) { // weekly first use - telemetryService.publicLog('telemetry.weeklyFirstUse', { weeklyFirstUse: true }); + telemetryService.sendTelemetryEvent('telemetry.weeklyFirstUse', { weeklyFirstUse: 'true' }); storageService.store('telemetry.weeklyLastUseDate', todayString, StorageScope.GLOBAL); } @@ -54,7 +55,7 @@ export class SqlTelemetryContribution extends Disposable implements IWorkbenchCo after the last time we sent a monthly usage count */ const monthlyUseCount: number = storageService.getNumber('telemetry.monthlyUseCount', StorageScope.GLOBAL, 0); if (this.didMonthChange(monthlyLastUseDate)) { - telemetryService.publicLog('telemetry.monthlyUse', { monthlyFirstUse: true }); + telemetryService.sendTelemetryEvent('telemetry.monthlyUse', { monthlyFirstUse: 'true' }); // the month changed, so send the user usage type event based on monthly count for last month // and reset the count for this month let lastMonthDate = new Date(monthlyLastUseDate); @@ -132,15 +133,15 @@ export class SqlTelemetryContribution extends Disposable implements IWorkbenchCo userUsageType = UserUsageType.Dedicated; } if (userUsageType) { - this.telemetryService.publicLog('telemetry.userUsage', - { userType: userUsageType, monthlyUseCount: monthlyUseCount, month: lastMonthDate.getMonth().toString(), year: lastMonthDate.getFullYear().toString() }); + this.telemetryService.sendTelemetryEvent('telemetry.userUsage', + { userType: userUsageType.toString(), monthlyUseCount: monthlyUseCount.toString(), month: lastMonthDate.getMonth().toString(), year: lastMonthDate.getFullYear().toString() }); } } // Growth Metrics private sendGrowthTypeEvent(growthType: UserGrowthType, lastMonthDate: Date): void { - this.telemetryService.publicLog('telemetry.userGrowthType', { - userGrowthType: growthType, month: lastMonthDate.getMonth().toString(), year: lastMonthDate.getFullYear().toString() + this.telemetryService.sendTelemetryEvent('telemetry.userGrowthType', { + userGrowthType: growthType.toString(), month: lastMonthDate.getMonth().toString(), year: lastMonthDate.getFullYear().toString() }); } } diff --git a/src/sql/platform/telemetry/telemetry.ts b/src/sql/platform/telemetry/telemetry.ts new file mode 100644 index 0000000000..c8f10bee44 --- /dev/null +++ b/src/sql/platform/telemetry/telemetry.ts @@ -0,0 +1,87 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IAdsTelemetryService = createDecorator('adsTelemetryService'); + +/** + * Holds additional properties to send along with an event. + */ +export interface ITelemetryEventProperties { + [key: string]: string; +} + +/** + * Holds additional measures to send along with an event. + */ +export interface ITelemetryEventMeasures { + [key: string]: number; +} + +/** + * Connection info properties to add into an event. + */ +export interface ITelemetryConnectionInfo { + authenticationType?: string; + providerName?: string; + serverType?: string; + engineType?: string; +} + +export interface ITelemetryEvent { + /** + * Sends the event + */ + send(): void; + + /** + * Adds additional custom properties to this event. + * @param additionalProperties The additional properties to add + */ + withAdditionalProperties(additionalProperties: ITelemetryEventProperties): ITelemetryEvent; + + /** + * Adds additional custom measurements to this event. + * @param additionalMeasurements The additional measurements to add + */ + withAdditionalMeasurements(additionalMeasurements: ITelemetryEventMeasures): ITelemetryEvent; + + /** + * Adds additional connection-related information to this event. + * @param connectionInfo The connection info to add. Only the fields in TelemetryConnectionInfo are included, all others are ignored. + */ + withConnectionInfo(connectionInfo: ITelemetryConnectionInfo): ITelemetryEvent; +} + +export interface ITelemetryInfo { + sessionId: string; + machineId: string; + instanceId: string; +} + +export interface IAdsTelemetryService { + + // ITelemetryService functions + _serviceBrand: any; + + setEnabled(value: boolean): void; + + getTelemetryInfo(): Promise; + + isOptedIn: boolean; + + // Custom event functions + createViewEvent(view: string): ITelemetryEvent; + sendViewEvent(view: string): void; + createActionEvent(view: string, action: string, target?: string, source?: string, durationInMs?: number): ITelemetryEvent; + sendActionEvent(view: string, action: string, target?: string, source?: string, durationInMs?: number): void; + createMetricsEvent(metrics: ITelemetryEventMeasures, groupName: string): ITelemetryEvent; + sendMetricsEvent(metrics: ITelemetryEventMeasures, groupName: string): void; + createErrorEvent(view: string, name: string, errorCode?: string, errorType?: string): ITelemetryEvent; + sendErrorEvent(view: string, name: string, errorCode?: string, errorType?: string): void; + createTelemetryEvent(eventName: string, properties?: ITelemetryEventProperties, measurements?: ITelemetryEventMeasures): ITelemetryEvent; + sendTelemetryEvent(eventName: string, properties?: ITelemetryEventProperties, measurements?: ITelemetryEventMeasures): void; +} diff --git a/src/sql/platform/telemetry/telemetryKeys.ts b/src/sql/platform/telemetry/telemetryKeys.ts index 76732d78e1..fb676998b7 100644 --- a/src/sql/platform/telemetry/telemetryKeys.ts +++ b/src/sql/platform/telemetry/telemetryKeys.ts @@ -62,3 +62,7 @@ export const DeleteAgentProxy = 'DeleteAgentProxy'; // Notebook Events: export const NotebookMarkdownRendered = 'NotebookMarkdownRendered'; + +export enum TelemetryView { + Shell = 'Shell' +} diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index fd7bcdb15d..7b7923e93b 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -47,6 +47,8 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia import { Layout } from 'vs/workbench/browser/layout'; import { ICommandLineProcessing } from 'sql/workbench/services/commandLine/common/commandLine'; import { CommandLineService } from 'sql/workbench/services/commandLine/common/commandLineService'; +import { IAdsTelemetryService } from 'sql/platform/telemetry/telemetry'; +import { AdsTelemetryService } from 'sql/platform/telemetry/adsTelemetryService'; export class Workbench extends Layout { diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 3851f1e7fb..70b50ee76a 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -231,6 +231,8 @@ import { DashboardService } from 'sql/platform/dashboard/browser/dashboardServic import { NotebookService } from 'sql/workbench/services/notebook/common/notebookServiceImpl'; import { INotebookService } from 'sql/workbench/services/notebook/common/notebookService'; import { OEShimService, IOEShimService } from 'sql/workbench/parts/objectExplorer/common/objectExplorerViewTreeShim'; +import { IAdsTelemetryService } from 'sql/platform/telemetry/telemetry'; +import { AdsTelemetryService } from 'sql/platform/telemetry/adsTelemetryService'; registerSingleton(IDashboardService, DashboardService); registerSingleton(IDashboardViewService, DashboardViewService); @@ -271,6 +273,7 @@ registerSingleton(IAccountPickerService, AccountPickerService); registerSingleton(IProfilerService, ProfilerService); registerSingleton(IDacFxService, DacFxService); registerSingleton(ISchemaCompareService, SchemaCompareService); +registerSingleton(IAdsTelemetryService, AdsTelemetryService); // {{SQL CARBON EDIT}} - End //#region --- workbench parts