Skip to content

Commit c02b695

Browse files
authored
Adding telemetry to webviews (#18053)
* Adding telemetry endpoints to webviews. * Changing time prop to duration * Fixing compile issue * Fixing typo
1 parent 47a4c0e commit c02b695

16 files changed

+307
-51
lines changed

src/controllers/connectionManager.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import { sendActionEvent, sendErrorEvent } from "../telemetry/telemetry";
3838
import {
3939
TelemetryActions,
4040
TelemetryViews,
41-
} from "../telemetry/telemetryInterfaces";
41+
} from "../sharedInterfaces/telemetry";
4242
import { ObjectExplorerUtils } from "../objectExplorer/objectExplorerUtils";
4343

4444
/**

src/controllers/mainController.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import { sendActionEvent } from "../telemetry/telemetry";
4747
import {
4848
TelemetryActions,
4949
TelemetryViews,
50-
} from "../telemetry/telemetryInterfaces";
50+
} from "../sharedInterfaces/telemetry";
5151
import { TableDesignerService } from "../services/tableDesignerService";
5252
import { TableDesignerWebviewController } from "../tableDesigner/tableDesignerWebviewController";
5353
import { ConnectionDialogWebviewController } from "../connectionconfig/connectionDialogWebviewController";
@@ -1719,7 +1719,7 @@ export default class MainController implements vscode.Disposable {
17191719
}
17201720
this._statusview.sqlCmdModeChanged(uri, false);
17211721
sendActionEvent(
1722-
TelemetryViews.CommandPallet,
1722+
TelemetryViews.CommandPalette,
17231723
TelemetryActions.NewQuery,
17241724
undefined,
17251725
undefined,

src/controllers/queryRunner.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ import { sendActionEvent } from "../telemetry/telemetry";
5151
import {
5252
TelemetryActions,
5353
TelemetryViews,
54-
} from "../telemetry/telemetryInterfaces";
54+
} from "../sharedInterfaces/telemetry";
5555

5656
export interface IResultSet {
5757
columns: string[];

src/controllers/reactWebviewBaseController.ts

+69-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55

66
import * as vscode from "vscode";
77
import { getNonce } from "../utils/utils";
8+
import { sendActionEvent, sendErrorEvent } from "../telemetry/telemetry";
9+
import {
10+
TelemetryActions,
11+
TelemetryViews,
12+
WebviewTelemetryActionEvent,
13+
WebviewTelemetryErrorEvent,
14+
} from "../sharedInterfaces/telemetry";
815

916
/**
1017
* ReactWebviewBaseController is a class that manages a vscode.Webview and provides
@@ -43,8 +50,25 @@ export abstract class ReactWebviewBaseController<State, Reducers>
4350
if (message.type === "request") {
4451
const handler = this._webviewRequestHandlers[message.method];
4552
if (handler) {
53+
const startTime = Date.now();
4654
const result = await handler(message.params);
4755
this.postMessage({ type: "response", id: message.id, result });
56+
const endTime = Date.now();
57+
sendActionEvent(
58+
TelemetryViews.WebviewController,
59+
TelemetryActions.WebviewRequest,
60+
{
61+
type: this._sourceFile,
62+
method: message.method,
63+
reducer:
64+
message.method === "action"
65+
? message.params.type
66+
: undefined,
67+
},
68+
{
69+
durationMs: endTime - startTime,
70+
},
71+
);
4872
} else {
4973
throw new Error(
5074
`No handler registered for method ${message.method}`,
@@ -137,6 +161,7 @@ export abstract class ReactWebviewBaseController<State, Reducers>
137161
this._webviewRequestHandlers["getState"] = () => {
138162
return this.state;
139163
};
164+
140165
this._webviewRequestHandlers["action"] = async (action) => {
141166
const reducer = this._reducers[action.type];
142167
if (reducer) {
@@ -147,20 +172,60 @@ export abstract class ReactWebviewBaseController<State, Reducers>
147172
);
148173
}
149174
};
175+
150176
this._webviewRequestHandlers["getTheme"] = () => {
151177
return vscode.window.activeColorTheme.kind;
152178
};
179+
153180
this._webviewRequestHandlers["loadStats"] = (message) => {
154181
const timeStamp = message.loadCompleteTimeStamp;
155182
const timeToLoad = timeStamp - this._loadStartTime;
156183
if (this._isFirstLoad) {
157-
console.log(`
158-
Load stats for ${this._sourceFile}
159-
Total time: ${timeToLoad} ms
160-
`);
184+
console.log(
185+
`Load stats for ${this._sourceFile}` +
186+
"\n" +
187+
`Total time: ${timeToLoad} ms`,
188+
);
189+
sendActionEvent(
190+
TelemetryViews.WebviewController,
191+
TelemetryActions.Load,
192+
{
193+
type: this._sourceFile,
194+
},
195+
{
196+
durationMs: timeToLoad,
197+
},
198+
);
161199
this._isFirstLoad = false;
162200
}
163201
};
202+
203+
this._webviewRequestHandlers["sendActionEvent"] = (
204+
message: WebviewTelemetryActionEvent,
205+
) => {
206+
sendActionEvent(
207+
message.telemetryView,
208+
message.telemetryAction,
209+
message.additionalProps,
210+
message.additionalMeasurements,
211+
);
212+
};
213+
214+
this._webviewRequestHandlers["sendErrorEvent"] = (
215+
message: WebviewTelemetryErrorEvent,
216+
) => {
217+
sendErrorEvent(
218+
message.telemetryView,
219+
message.telemetryAction,
220+
message.error,
221+
message.includeErrorMessage,
222+
message.errorCode,
223+
message.errorType,
224+
message.additionalProps,
225+
message.additionalMeasurements,
226+
);
227+
};
228+
164229
this._webviewRequestHandlers["getLocalization"] = async () => {
165230
if (vscode.l10n.uri?.fsPath) {
166231
const file = await vscode.workspace.fs.readFile(

src/models/connectionProfile.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import { AzureAuthType, IAccount, ITenant } from "./contracts/azure";
2222
import { getEnableSqlAuthenticationProviderConfig } from "../azure/utils";
2323
import { sendActionEvent } from "../telemetry/telemetry";
2424
import {
25-
TelemetryActions,
2625
TelemetryViews,
27-
} from "../telemetry/telemetryInterfaces";
26+
TelemetryActions,
27+
} from "../sharedInterfaces/telemetry";
2828

2929
// Concrete implementation of the IConnectionProfile interface
3030

src/models/sqlOutputContentProvider.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ import { WebviewPanelController } from "../controllers/webviewController";
1616
import { IServerProxy, Deferred } from "../protocol";
1717
import { ResultSetSubset, ResultSetSummary } from "./contracts/queryExecute";
1818
import { sendActionEvent } from "../telemetry/telemetry";
19+
import { QueryResultWebviewController } from "../queryResult/queryResultWebViewController";
20+
import { QueryResultPaneTabs } from "../sharedInterfaces/queryResult";
1921
import {
2022
TelemetryActions,
2123
TelemetryViews,
22-
} from "../telemetry/telemetryInterfaces";
23-
import { QueryResultWebviewController } from "../queryResult/queryResultWebViewController";
24-
import { QueryResultPaneTabs } from "../sharedInterfaces/queryResult";
24+
} from "../sharedInterfaces/telemetry";
2525
// tslint:disable-next-line:no-require-imports
2626
const pd = require("pretty-data").pd;
2727

src/objectExplorer/objectExplorerFilter.ts

+42
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ import {
1111
ObjectExplorerFilterState,
1212
ObjectExplorerReducers,
1313
} from "../sharedInterfaces/objectExplorerFilter";
14+
import { sendActionEvent } from "../telemetry/telemetry";
15+
import {
16+
TelemetryActions,
17+
TelemetryViews,
18+
} from "../sharedInterfaces/telemetry";
19+
import { randomUUID } from "crypto";
1420

1521
export class ObjectExplorerFilterReactWebviewController extends ReactWebviewPanelController<
1622
ObjectExplorerFilterState,
@@ -84,6 +90,15 @@ export class ObjectExplorerFilter {
8490
treeNode: TreeNodeInfo,
8591
): Promise<vscodeMssql.NodeFilter[] | undefined> {
8692
return await new Promise((resolve, _reject) => {
93+
const correlationId = randomUUID();
94+
sendActionEvent(
95+
TelemetryViews.ObjectExplorerFilter,
96+
TelemetryActions.Open,
97+
{
98+
nodeType: treeNode.nodeType,
99+
correlationId,
100+
},
101+
);
87102
if (
88103
!this._filterWebviewController ||
89104
this._filterWebviewController.isDisposed
@@ -103,12 +118,39 @@ export class ObjectExplorerFilter {
103118
}
104119
this._filterWebviewController.revealToForeground();
105120
this._filterWebviewController.onSubmit((e) => {
121+
sendActionEvent(
122+
TelemetryViews.ObjectExplorerFilter,
123+
TelemetryActions.Submit,
124+
{
125+
nodeType: treeNode.nodeType,
126+
correlationId,
127+
},
128+
{
129+
filterCount: e.length,
130+
},
131+
);
106132
resolve(e);
107133
});
108134
this._filterWebviewController.onCancel(() => {
135+
sendActionEvent(
136+
TelemetryViews.ObjectExplorerFilter,
137+
TelemetryActions.Cancel,
138+
{
139+
nodeType: treeNode.nodeType,
140+
correlationId,
141+
},
142+
);
109143
resolve(undefined);
110144
});
111145
this._filterWebviewController.onDisposed(() => {
146+
sendActionEvent(
147+
TelemetryViews.ObjectExplorerFilter,
148+
TelemetryActions.Cancel,
149+
{
150+
nodeType: treeNode.nodeType,
151+
correlationId,
152+
},
153+
);
112154
resolve(undefined);
113155
});
114156
});

src/objectExplorer/objectExplorerService.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ import { IConnectionInfo } from "vscode-mssql";
4747
import { sendActionEvent } from "../telemetry/telemetry";
4848
import { IAccount } from "../models/contracts/azure";
4949
import * as AzureConstants from "../azure/constants";
50+
import { getConnectionDisplayName } from "../models/connectionInfo";
5051
import {
5152
TelemetryActions,
5253
TelemetryViews,
53-
} from "../telemetry/telemetryInterfaces";
54-
import { getConnectionDisplayName } from "../models/connectionInfo";
54+
} from "../sharedInterfaces/telemetry";
5555

5656
function getParentNode(node: TreeNodeType): TreeNodeInfo {
5757
node = node.parentNode;

src/protocol.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ import {
1010
ResultSetSubset,
1111
ISelectionData,
1212
} from "./models/interfaces";
13-
import {
14-
TelemetryActions,
15-
TelemetryViews,
16-
} from "./telemetry/telemetryInterfaces";
13+
import { TelemetryViews, TelemetryActions } from "./sharedInterfaces/telemetry";
1714

1815
export interface IWebviewProxy extends Disposable {
1916
sendEvent(type: string, arg: any): void;

src/reactviews/common/rpc.ts

+12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { WebviewApi } from "vscode-webview";
7+
import {
8+
WebviewTelemetryActionEvent,
9+
WebviewTelemetryErrorEvent,
10+
} from "../../sharedInterfaces/telemetry";
711

812
/**
913
* Rpc to communicate with the extension.
@@ -88,4 +92,12 @@ export class WebviewRpc<Reducers> {
8892
}
8993
this._methodSubscriptions[method].push(callback);
9094
}
95+
96+
public sendActionEvent(event: WebviewTelemetryActionEvent) {
97+
this.call("sendActionEvent", event);
98+
}
99+
100+
public sendErrorEvent(event: WebviewTelemetryErrorEvent) {
101+
this.call("sendErrorEvent", event);
102+
}
91103
}

src/sharedInterfaces/telemetry.ts

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
export enum TelemetryViews {
7+
ObjectExplorer = "ObjectExplorer",
8+
CommandPalette = "CommandPalette",
9+
SqlProjects = "SqlProjects",
10+
QueryEditor = "QueryEditor",
11+
ResultsGrid = "ResultsGrid",
12+
ConnectionPrompt = "ConnectionPrompt",
13+
WebviewController = "WebviewController",
14+
ObjectExplorerFilter = "ObjectExplorerFilter",
15+
TableDesigner = "TableDesigner",
16+
}
17+
18+
export enum TelemetryActions {
19+
GenerateScript = "GenerateScript",
20+
Refresh = "Refresh",
21+
CreateProject = "CreateProject",
22+
RemoveConnection = "RemoveConnection",
23+
Disconnect = "Disconnect",
24+
NewQuery = "NewQuery",
25+
RunQuery = "RunQuery",
26+
QueryExecutionCompleted = "QueryExecutionCompleted",
27+
RunResultPaneAction = "RunResultPaneAction",
28+
CreateConnection = "CreateConnection",
29+
CreateConnectionResult = "CreateConnectionResult",
30+
ExpandNode = "ExpandNode",
31+
ResultPaneAction = "ResultPaneAction",
32+
Load = "Load",
33+
WebviewRequest = "WebviewRequest",
34+
Open = "Open",
35+
Submit = "Submit",
36+
Cancel = "Cancel",
37+
Initialize = "Initialize",
38+
Edit = "Edit",
39+
Publish = "Publish",
40+
ContinueEditing = "ContinueEditing",
41+
Close = "Close",
42+
}
43+
44+
export interface WebviewTelemetryActionEvent {
45+
/**
46+
* The view in which the event occurred.
47+
*/
48+
telemetryView: TelemetryViews;
49+
/**
50+
* The action that was being performed when the event occurred.
51+
*/
52+
telemetryAction: TelemetryActions;
53+
/**
54+
* Additional properties for the event.
55+
*/
56+
additionalProps?: Record<string, string>;
57+
/**
58+
* Additional measurements for the event.
59+
*/
60+
additionalMeasurements?: Record<string, number>;
61+
}
62+
63+
export interface WebviewTelemetryErrorEvent {
64+
/**
65+
* The view in which the event occurred.
66+
*/
67+
telemetryView: TelemetryViews;
68+
/**
69+
* The action that was being performed when the event occurred.
70+
*/
71+
telemetryAction: TelemetryActions;
72+
/**
73+
* Error that occurred.
74+
*/
75+
error: Error;
76+
/**
77+
* Whether to include the error message in the telemetry event. Defaults to false.
78+
*/
79+
includeErrorMessage: boolean;
80+
/**
81+
* Error code for the error.
82+
*/
83+
errorCode?: string;
84+
/**
85+
* Error type for the error.
86+
*/
87+
errorType?: string;
88+
/**
89+
* Additional properties to include in the telemetry event.
90+
*/
91+
additionalProps?: Record<string, string>;
92+
/**
93+
* Additional measurements to include in the telemetry event.
94+
*/
95+
additionalMeasurements?: Record<string, number>;
96+
}

0 commit comments

Comments
 (0)