Skip to content

Commit afdb47d

Browse files
committed
feat(telemetry): integrate telemetry tracking for vscode extension
1 parent 7ac1503 commit afdb47d

File tree

4 files changed

+84
-4
lines changed

4 files changed

+84
-4
lines changed

packages/schema/src/extension.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { AUTH_PROVIDER_ID, ZenStackAuthenticationProvider } from './zenstack-aut
66
import { DocumentationCache } from './documentation-cache';
77
import { ZModelPreview } from './zmodel-preview';
88
import { ReleaseNotesManager } from './release-notes-manager';
9+
import telemetry from './vscode-telemetry';
910

1011
// Global variables
1112
let client: LanguageClient;
@@ -19,13 +20,17 @@ export async function requireAuth(): Promise<vscode.AuthenticationSession | unde
1920
if (!session) {
2021
const signIn = 'Sign in';
2122
const selection = await vscode.window.showWarningMessage('Please sign in to use this feature', signIn);
23+
telemetry.track('extension:signin:show');
2224
if (selection === signIn) {
25+
telemetry.track('extension:signin:start');
2326
try {
2427
session = await vscode.authentication.getSession(AUTH_PROVIDER_ID, [], { createIfNone: true });
2528
if (session) {
29+
telemetry.track('extension:signin:complete');
2630
vscode.window.showInformationMessage('ZenStack sign-in successful!');
2731
}
2832
} catch (e: unknown) {
33+
telemetry.track('extension:signin:error', { error: e instanceof Error ? e.message : String(e) });
2934
vscode.window.showErrorMessage(
3035
'ZenStack sign-in failed: ' + (e instanceof Error ? e.message : String(e))
3136
);
@@ -37,6 +42,7 @@ export async function requireAuth(): Promise<vscode.AuthenticationSession | unde
3742

3843
// This function is called when the extension is activated.
3944
export function activate(context: vscode.ExtensionContext): void {
45+
telemetry.track('extension:activate');
4046
// Initialize and register the ZenStack authentication provider
4147
context.subscriptions.push(new ZenStackAuthenticationProvider(context));
4248

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { init, Mixpanel } from 'mixpanel';
2+
import * as os from 'os';
3+
import vscode from 'vscode';
4+
import { getMachineId } from './utils/machine-id-utils';
5+
import { v5 as uuidv5 } from 'uuid';
6+
import { TELEMETRY_TRACKING_TOKEN } from './constants';
7+
8+
export type TelemetryEvents =
9+
| 'extension:activate'
10+
| 'extension:zmodel-preview'
11+
| 'extension:zmodel-save'
12+
| 'extension:signin:show'
13+
| 'extension:signin:start'
14+
| 'extension:signin:error'
15+
| 'extension:signin:complete';
16+
17+
export class VSCodeTelemetry {
18+
private readonly mixpanel: Mixpanel | undefined;
19+
private readonly deviceId = this.getDeviceId();
20+
private readonly _os_type = os.type();
21+
private readonly _os_release = os.release();
22+
private readonly _os_arch = os.arch();
23+
private readonly _os_version = os.version();
24+
private readonly _os_platform = os.platform();
25+
private readonly vscodeAppName = vscode.env.appName;
26+
private readonly vscodeVersion = vscode.version;
27+
private readonly vscodeAppHost = vscode.env.appHost;
28+
29+
constructor() {
30+
if (vscode.env.isTelemetryEnabled) {
31+
this.mixpanel = init(TELEMETRY_TRACKING_TOKEN, {
32+
geolocate: true,
33+
});
34+
}
35+
}
36+
37+
private getDeviceId() {
38+
const hostId = getMachineId();
39+
return uuidv5(hostId, '133cac15-3efb-50fa-b5fc-4b90e441e563');
40+
}
41+
42+
track(event: TelemetryEvents, properties: Record<string, unknown> = {}) {
43+
if (this.mixpanel) {
44+
const payload = {
45+
distinct_id: this.deviceId,
46+
time: new Date(),
47+
$os: this._os_type,
48+
osType: this._os_type,
49+
osRelease: this._os_release,
50+
osPlatform: this._os_platform,
51+
osArch: this._os_arch,
52+
osVersion: this._os_version,
53+
nodeVersion: process.version,
54+
vscodeAppName: this.vscodeAppName,
55+
vscodeVersion: this.vscodeVersion,
56+
vscodeAppHost: this.vscodeAppHost,
57+
...properties,
58+
};
59+
this.mixpanel.track(event, payload);
60+
}
61+
}
62+
63+
identify(userId: string) {
64+
if (this.mixpanel) {
65+
this.mixpanel.track('$identify', {
66+
$identified_id: userId,
67+
$anon_id: this.deviceId,
68+
token: TELEMETRY_TRACKING_TOKEN,
69+
});
70+
}
71+
}
72+
}
73+
export default new VSCodeTelemetry();

packages/schema/src/zenstack-auth-provider.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as vscode from 'vscode';
2-
2+
import telemetry from './vscode-telemetry';
33
interface JWTClaims {
44
jti?: string;
55
sub?: string;
@@ -150,9 +150,6 @@ export class ZenStackAuthenticationProvider implements vscode.AuthenticationProv
150150
}
151151
);
152152
}
153-
private generateState(): string {
154-
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
155-
}
156153

157154
// Handle authentication callback from ZenStack
158155
public async handleAuthCallback(callbackUri: vscode.Uri): Promise<void> {
@@ -184,6 +181,7 @@ export class ZenStackAuthenticationProvider implements vscode.AuthenticationProv
184181
try {
185182
// Decode JWT to get claims
186183
const claims = this.parseJWTClaims(accessToken);
184+
telemetry.identify(claims.email!);
187185
return {
188186
id: claims.jti || Math.random().toString(36),
189187
accessToken: accessToken,

packages/schema/src/zmodel-preview.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { URI } from 'vscode-uri';
77
import { DocumentationCache } from './documentation-cache';
88
import { requireAuth } from './extension';
99
import { API_URL } from './zenstack-auth-provider';
10+
import telemetry from './vscode-telemetry';
1011

1112
/**
1213
* ZModelPreview class handles ZModel file preview functionality
@@ -92,6 +93,7 @@ export class ZModelPreview implements vscode.Disposable {
9293
* Preview a ZModel file
9394
*/
9495
async previewZModelFile(): Promise<void> {
96+
telemetry.track('extension:zmodel-preview');
9597
const editor = vscode.window.activeTextEditor;
9698

9799
if (!editor) {
@@ -285,6 +287,7 @@ export class ZModelPreview implements vscode.Disposable {
285287
* Save ZModel documentation to a user-selected file
286288
*/
287289
async saveZModelDocumentation(): Promise<void> {
290+
telemetry.track('extension:zmodel-save');
288291
// Check if we have cached content first
289292
if (!this.lastGeneratedMarkdown) {
290293
vscode.window.showErrorMessage(

0 commit comments

Comments
 (0)