mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
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
This commit is contained in:
@@ -70,7 +70,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vscode-extension-telemetry": "^0.0.15",
|
"ads-extension-telemetry": "github:Charles-Gagnon/ads-extension-telemetry#0.1.0",
|
||||||
"vscode-nls": "^3.2.1"
|
"vscode-nls": "^3.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as nls from 'vscode-nls';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { Telemetry } from './telemetry';
|
import { TelemetryReporter, TelemetryViews } from './telemetry';
|
||||||
import { doubleEscapeSingleQuotes, backEscapeDoubleQuotes, getTelemetryErrorType } from './utils';
|
import { doubleEscapeSingleQuotes, backEscapeDoubleQuotes, getTelemetryErrorType } from './utils';
|
||||||
import { ChildProcess, exec } from 'child_process';
|
import { ChildProcess, exec } from 'child_process';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
@@ -95,7 +95,7 @@ function registerCommands(context: vscode.ExtensionContext): void {
|
|||||||
*/
|
*/
|
||||||
async function handleLaunchSsmsMinPropertiesDialogCommand(connectionContext?: azdata.ObjectExplorerContext): Promise<void> {
|
async function handleLaunchSsmsMinPropertiesDialogCommand(connectionContext?: azdata.ObjectExplorerContext): Promise<void> {
|
||||||
if (!connectionContext) {
|
if (!connectionContext) {
|
||||||
Telemetry.sendTelemetryEventForError('NoConnectionContext', { action: 'Properties' });
|
TelemetryReporter.sendErrorEvent(TelemetryViews.SsmsMinProperties, 'NoConnectionContext');
|
||||||
vscode.window.showErrorMessage(localize('adminToolExtWin.noConnectionContextForProp', 'No ConnectionContext provided for handleLaunchSsmsMinPropertiesDialogCommand'));
|
vscode.window.showErrorMessage(localize('adminToolExtWin.noConnectionContextForProp', 'No ConnectionContext provided for handleLaunchSsmsMinPropertiesDialogCommand'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ async function handleLaunchSsmsMinPropertiesDialogCommand(connectionContext?: az
|
|||||||
else if (connectionContext.nodeInfo) {
|
else if (connectionContext.nodeInfo) {
|
||||||
nodeType = connectionContext.nodeInfo.nodeType;
|
nodeType = connectionContext.nodeInfo.nodeType;
|
||||||
} else {
|
} 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)));
|
vscode.window.showErrorMessage(localize('adminToolExtWin.noOENode', 'Could not determine Object Explorer node from connectionContext : {0}', JSON.stringify(connectionContext)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -124,7 +124,7 @@ async function handleLaunchSsmsMinPropertiesDialogCommand(connectionContext?: az
|
|||||||
async function handleLaunchSsmsMinGswDialogCommand(connectionContext?: azdata.ObjectExplorerContext): Promise<void> {
|
async function handleLaunchSsmsMinGswDialogCommand(connectionContext?: azdata.ObjectExplorerContext): Promise<void> {
|
||||||
const action = 'GenerateScripts';
|
const action = 'GenerateScripts';
|
||||||
if (!connectionContext) {
|
if (!connectionContext) {
|
||||||
Telemetry.sendTelemetryEventForError('NoConnectionContext', { action: action });
|
TelemetryReporter.sendErrorEvent(TelemetryViews.SsmsMinGsw, 'NoConnectionContext');
|
||||||
vscode.window.showErrorMessage(localize('adminToolExtWin.noConnectionContextForGsw', 'No ConnectionContext provided for handleLaunchSsmsMinPropertiesDialogCommand'));
|
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<void> {
|
async function launchSsmsDialog(action: string, connectionContext: azdata.ObjectExplorerContext): Promise<void> {
|
||||||
if (!connectionContext.connectionProfile) {
|
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)));
|
vscode.window.showErrorMessage(localize('adminToolExtWin.noConnectionProfile', 'No connectionProfile provided from connectionContext : {0}', JSON.stringify(connectionContext)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -155,7 +155,7 @@ async function launchSsmsDialog(action: string, connectionContext: azdata.Object
|
|||||||
oeNode = await azdata.objectexplorer.getNode(connectionContext.connectionProfile.id, connectionContext.nodeInfo.nodePath);
|
oeNode = await azdata.objectexplorer.getNode(connectionContext.connectionProfile.id, connectionContext.nodeInfo.nodePath);
|
||||||
}
|
}
|
||||||
else {
|
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)));
|
vscode.window.showErrorMessage(localize('adminToolExtWin.noOENode', 'Could not determine Object Explorer node from connectionContext : {0}', JSON.stringify(connectionContext)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -178,13 +178,15 @@ async function launchSsmsDialog(action: string, connectionContext: azdata.Object
|
|||||||
};
|
};
|
||||||
|
|
||||||
const args = buildSsmsMinCommandArgs(params);
|
const args = buildSsmsMinCommandArgs(params);
|
||||||
|
TelemetryReporter.createActionEvent(
|
||||||
Telemetry.sendTelemetryEvent('LaunchSsmsDialog',
|
TelemetryViews.SsmsMinDialog,
|
||||||
|
'LaunchSsmsDialog',
|
||||||
|
'',
|
||||||
|
action).withAdditionalProperties(
|
||||||
{
|
{
|
||||||
action: action,
|
nodeType: oeNode ? oeNode.nodeType : 'Server'
|
||||||
nodeType: oeNode ? oeNode.nodeType : 'Server',
|
}).withConnectionInfo(connectionContext.connectionProfile)
|
||||||
authType: connectionContext.connectionProfile.authenticationType
|
.send();
|
||||||
});
|
|
||||||
|
|
||||||
vscode.window.setStatusBarMessage(localize('adminToolExtWin.launchingDialogStatus', 'Launching dialog...'), 3000);
|
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
|
// Process has exited so remove from map of running processes
|
||||||
runningProcesses.delete(proc.pid);
|
runningProcesses.delete(proc.pid);
|
||||||
const err = stderr.toString();
|
const err = stderr.toString();
|
||||||
Telemetry.sendTelemetryEvent('LaunchSsmsDialogResult', {
|
if ((execException && execException.code !== 0) || err !== '') {
|
||||||
action: params.action,
|
TelemetryReporter.sendErrorEvent(
|
||||||
returnCode: execException && execException.code ? execException.code.toString() : '0',
|
TelemetryViews.SsmsMinDialog,
|
||||||
errorType: getTelemetryErrorType(err)
|
'LaunchSsmsDialogError',
|
||||||
});
|
execException ? execException.code.toString() : '',
|
||||||
|
getTelemetryErrorType(err));
|
||||||
|
}
|
||||||
|
|
||||||
if (err !== '') {
|
if (err !== '') {
|
||||||
vscode.window.showErrorMessage(localize(
|
vscode.window.showErrorMessage(localize(
|
||||||
'adminToolExtWin.ssmsMinError',
|
'adminToolExtWin.ssmsMinError',
|
||||||
|
|||||||
@@ -4,99 +4,23 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
import * as vscode from 'vscode';
|
import AdsTelemetryReporter from 'ads-extension-telemetry';
|
||||||
import TelemetryReporter from 'vscode-extension-telemetry';
|
|
||||||
|
|
||||||
import * as Utils from './utils';
|
import * as Utils from './utils';
|
||||||
|
|
||||||
const packageJson = require('../package.json');
|
const packageJson = require('../package.json');
|
||||||
|
|
||||||
export interface ITelemetryEventProperties {
|
|
||||||
[key: string]: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
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<boolean>('enableTelemetry', true)) {
|
|
||||||
this.disable();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let packageInfo = Utils.getPackageInfo(packageJson);
|
let packageInfo = Utils.getPackageInfo(packageJson);
|
||||||
this.reporter = new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
|
|
||||||
}
|
export const TelemetryReporter = new AdsTelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
|
||||||
|
|
||||||
|
export enum TelemetryViews {
|
||||||
|
SsmsMinProperties = 'SsmsMinProperties',
|
||||||
|
SsmsMinGsw = 'SsmsMinGsw',
|
||||||
|
SsmsMinDialog = 'SsmsMinDialog'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
# yarn lockfile v1
|
# 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:
|
ajv@^6.5.5:
|
||||||
version "6.9.2"
|
version "6.9.2"
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.2.tgz#4927adb83e7f48e5a32b45729744c71ec39c9c7b"
|
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"
|
resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
|
||||||
integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768=
|
integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768=
|
||||||
|
|
||||||
applicationinsights@1.0.1:
|
applicationinsights@1.0.8:
|
||||||
version "1.0.1"
|
version "1.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
|
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5"
|
||||||
integrity sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=
|
integrity sha512-KzOOGdphOS/lXWMFZe5440LUdFbrLpMvh2SaRxn7BmiI550KAoSb2gIhiq6kJZ9Ir3AxRRztjhzif+e5P5IXIg==
|
||||||
dependencies:
|
dependencies:
|
||||||
diagnostic-channel "0.2.0"
|
diagnostic-channel "0.2.0"
|
||||||
diagnostic-channel-publishers "0.2.1"
|
diagnostic-channel-publishers "0.2.1"
|
||||||
@@ -2271,12 +2277,12 @@ vinyl@~2.0.1:
|
|||||||
remove-trailing-separator "^1.0.1"
|
remove-trailing-separator "^1.0.1"
|
||||||
replace-ext "^1.0.0"
|
replace-ext "^1.0.0"
|
||||||
|
|
||||||
vscode-extension-telemetry@^0.0.15:
|
vscode-extension-telemetry@0.1.1:
|
||||||
version "0.0.15"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.15.tgz#685c32f3b67e8fb85ba689c1d7f88ff90ff87856"
|
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.1.tgz#91387e06b33400c57abd48979b0e790415ae110b"
|
||||||
integrity sha512-Yf6dL9r2x2GISI1xh22XsAaydSTQG/4aBitu8sGBwGr42n2TyOsIXGtXSDgqQBNZgYD6+P1EHqrrzetn9ekWTQ==
|
integrity sha512-TkKKG/B/J94DP5qf6xWB4YaqlhWDg6zbbqVx7Bz//stLQNnfE9XS1xm3f6fl24c5+bnEK0/wHgMgZYKIKxPeUA==
|
||||||
dependencies:
|
dependencies:
|
||||||
applicationinsights "1.0.1"
|
applicationinsights "1.0.8"
|
||||||
|
|
||||||
vscode-nls@^3.2.1:
|
vscode-nls@^3.2.1:
|
||||||
version "3.2.5"
|
version "3.2.5"
|
||||||
|
|||||||
189
src/sql/platform/telemetry/adsTelemetryService.ts
Normal file
189
src/sql/platform/telemetry/adsTelemetryService.ts
Normal file
@@ -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<ITelemetryInfo> {
|
||||||
|
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 <extension-name>/
|
||||||
|
* @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 <extension-name>/
|
||||||
|
* @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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,11 @@
|
|||||||
* 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 { TelemetryView } from 'sql/platform/telemetry/telemetryKeys';
|
||||||
|
import { IAdsTelemetryService } from 'sql/platform/telemetry/telemetry';
|
||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||||
import { ICommandService, ICommandEvent } from 'vs/platform/commands/common/commands';
|
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 {
|
export class SqlTelemetryContribution extends Disposable implements IWorkbenchContribution {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ITelemetryService private telemetryService: ITelemetryService,
|
@IAdsTelemetryService private telemetryService: IAdsTelemetryService,
|
||||||
@IStorageService storageService: IStorageService,
|
@IStorageService storageService: IStorageService,
|
||||||
@ICommandService commandService: ICommandService
|
@ICommandService commandService: ICommandService
|
||||||
) {
|
) {
|
||||||
@@ -25,7 +26,7 @@ export class SqlTelemetryContribution extends Disposable implements IWorkbenchCo
|
|||||||
(e: ICommandEvent) => {
|
(e: ICommandEvent) => {
|
||||||
// Filter out high-frequency events
|
// Filter out high-frequency events
|
||||||
if (!['type', 'cursorUp', 'cursorDown', 'cursorRight', 'cursorLeft', 'deleteLeft', 'deleteRight'].find(id => id === e.commandId)) {
|
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'));
|
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
|
// daily user event
|
||||||
if (this.didDayChange(dailyLastUseDate)) {
|
if (this.didDayChange(dailyLastUseDate)) {
|
||||||
// daily first use
|
// daily first use
|
||||||
telemetryService.publicLog('telemetry.dailyFirstUse', { dailyFirstUse: true });
|
telemetryService.sendTelemetryEvent('telemetry.dailyFirstUse', { dailyFirstUse: 'true' });
|
||||||
storageService.store('telemetry.dailyLastUseDate', todayString, StorageScope.GLOBAL);
|
storageService.store('telemetry.dailyLastUseDate', todayString, StorageScope.GLOBAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// weekly user event
|
// weekly user event
|
||||||
if (this.didWeekChange(weeklyLastUseDate)) {
|
if (this.didWeekChange(weeklyLastUseDate)) {
|
||||||
// weekly first use
|
// weekly first use
|
||||||
telemetryService.publicLog('telemetry.weeklyFirstUse', { weeklyFirstUse: true });
|
telemetryService.sendTelemetryEvent('telemetry.weeklyFirstUse', { weeklyFirstUse: 'true' });
|
||||||
storageService.store('telemetry.weeklyLastUseDate', todayString, StorageScope.GLOBAL);
|
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 */
|
after the last time we sent a monthly usage count */
|
||||||
const monthlyUseCount: number = storageService.getNumber('telemetry.monthlyUseCount', StorageScope.GLOBAL, 0);
|
const monthlyUseCount: number = storageService.getNumber('telemetry.monthlyUseCount', StorageScope.GLOBAL, 0);
|
||||||
if (this.didMonthChange(monthlyLastUseDate)) {
|
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
|
// the month changed, so send the user usage type event based on monthly count for last month
|
||||||
// and reset the count for this month
|
// and reset the count for this month
|
||||||
let lastMonthDate = new Date(monthlyLastUseDate);
|
let lastMonthDate = new Date(monthlyLastUseDate);
|
||||||
@@ -132,15 +133,15 @@ export class SqlTelemetryContribution extends Disposable implements IWorkbenchCo
|
|||||||
userUsageType = UserUsageType.Dedicated;
|
userUsageType = UserUsageType.Dedicated;
|
||||||
}
|
}
|
||||||
if (userUsageType) {
|
if (userUsageType) {
|
||||||
this.telemetryService.publicLog('telemetry.userUsage',
|
this.telemetryService.sendTelemetryEvent('telemetry.userUsage',
|
||||||
{ userType: userUsageType, monthlyUseCount: monthlyUseCount, month: lastMonthDate.getMonth().toString(), year: lastMonthDate.getFullYear().toString() });
|
{ userType: userUsageType.toString(), monthlyUseCount: monthlyUseCount.toString(), month: lastMonthDate.getMonth().toString(), year: lastMonthDate.getFullYear().toString() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Growth Metrics
|
// Growth Metrics
|
||||||
private sendGrowthTypeEvent(growthType: UserGrowthType, lastMonthDate: Date): void {
|
private sendGrowthTypeEvent(growthType: UserGrowthType, lastMonthDate: Date): void {
|
||||||
this.telemetryService.publicLog('telemetry.userGrowthType', {
|
this.telemetryService.sendTelemetryEvent('telemetry.userGrowthType', {
|
||||||
userGrowthType: growthType, month: lastMonthDate.getMonth().toString(), year: lastMonthDate.getFullYear().toString()
|
userGrowthType: growthType.toString(), month: lastMonthDate.getMonth().toString(), year: lastMonthDate.getFullYear().toString()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
87
src/sql/platform/telemetry/telemetry.ts
Normal file
87
src/sql/platform/telemetry/telemetry.ts
Normal file
@@ -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<IAdsTelemetryService>('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<ITelemetryInfo>;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -62,3 +62,7 @@ export const DeleteAgentProxy = 'DeleteAgentProxy';
|
|||||||
|
|
||||||
// Notebook Events:
|
// Notebook Events:
|
||||||
export const NotebookMarkdownRendered = 'NotebookMarkdownRendered';
|
export const NotebookMarkdownRendered = 'NotebookMarkdownRendered';
|
||||||
|
|
||||||
|
export enum TelemetryView {
|
||||||
|
Shell = 'Shell'
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia
|
|||||||
import { Layout } from 'vs/workbench/browser/layout';
|
import { Layout } from 'vs/workbench/browser/layout';
|
||||||
import { ICommandLineProcessing } from 'sql/workbench/services/commandLine/common/commandLine';
|
import { ICommandLineProcessing } from 'sql/workbench/services/commandLine/common/commandLine';
|
||||||
import { CommandLineService } from 'sql/workbench/services/commandLine/common/commandLineService';
|
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 {
|
export class Workbench extends Layout {
|
||||||
|
|
||||||
|
|||||||
@@ -231,6 +231,8 @@ import { DashboardService } from 'sql/platform/dashboard/browser/dashboardServic
|
|||||||
import { NotebookService } from 'sql/workbench/services/notebook/common/notebookServiceImpl';
|
import { NotebookService } from 'sql/workbench/services/notebook/common/notebookServiceImpl';
|
||||||
import { INotebookService } from 'sql/workbench/services/notebook/common/notebookService';
|
import { INotebookService } from 'sql/workbench/services/notebook/common/notebookService';
|
||||||
import { OEShimService, IOEShimService } from 'sql/workbench/parts/objectExplorer/common/objectExplorerViewTreeShim';
|
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(IDashboardService, DashboardService);
|
||||||
registerSingleton(IDashboardViewService, DashboardViewService);
|
registerSingleton(IDashboardViewService, DashboardViewService);
|
||||||
@@ -271,6 +273,7 @@ registerSingleton(IAccountPickerService, AccountPickerService);
|
|||||||
registerSingleton(IProfilerService, ProfilerService);
|
registerSingleton(IProfilerService, ProfilerService);
|
||||||
registerSingleton(IDacFxService, DacFxService);
|
registerSingleton(IDacFxService, DacFxService);
|
||||||
registerSingleton(ISchemaCompareService, SchemaCompareService);
|
registerSingleton(ISchemaCompareService, SchemaCompareService);
|
||||||
|
registerSingleton(IAdsTelemetryService, AdsTelemetryService);
|
||||||
// {{SQL CARBON EDIT}} - End
|
// {{SQL CARBON EDIT}} - End
|
||||||
|
|
||||||
//#region --- workbench parts
|
//#region --- workbench parts
|
||||||
|
|||||||
Reference in New Issue
Block a user