Skip to content

Commit 6dff52f

Browse files
Port experimentation service and reporting to extension (#3675)
1 parent 0fa4110 commit 6dff52f

8 files changed

Lines changed: 130 additions & 14 deletions

File tree

_extension/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@
223223
},
224224
"dependencies": {
225225
"@vscode/extension-telemetry": "^1.5.1",
226-
"vscode-languageclient": "^10.0.0-next.21"
226+
"vscode-languageclient": "^10.0.0-next.21",
227+
"vscode-tas-client": "0.1.84"
227228
},
228229
"devDependencies": {
229230
"@types/vscode": "~1.110.0",

_extension/src/commands.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import type {
66
Position,
77
} from "vscode-languageclient";
88

9-
import { Client } from "./client";
109
import type * as tr from "./telemetryReporting";
1110
import { restartExtHostOnChangeIfNeeded } from "./util";
1211

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import * as vscode from "vscode";
2+
import * as tas from "vscode-tas-client";
3+
4+
interface ExperimentTypes {
5+
// Experiment variables go here.
6+
suggestNativePreview: boolean;
7+
}
8+
9+
export class ExperimentationService {
10+
private readonly _experimentationService: tas.IExperimentationService;
11+
private readonly _telemetryReporter: tas.IExperimentationTelemetry;
12+
13+
constructor(telemetryReporter: tas.IExperimentationTelemetry, id: string, version: string, globalState: vscode.Memento) {
14+
this._telemetryReporter = telemetryReporter;
15+
this._experimentationService = createTasExperimentationService(this._telemetryReporter, id, version, globalState);
16+
}
17+
18+
public async getTreatmentVariable<K extends keyof ExperimentTypes>(name: K, defaultValue: ExperimentTypes[K]): Promise<ExperimentTypes[K]> {
19+
const experimentationService = this._experimentationService;
20+
try {
21+
const treatmentVariable = await experimentationService.getTreatmentVariableAsync("vscode", name, /*checkCache*/ true) as ExperimentTypes[K];
22+
return treatmentVariable ?? defaultValue;
23+
}
24+
catch {
25+
return defaultValue;
26+
}
27+
}
28+
}
29+
30+
function createTasExperimentationService(
31+
reporter: tas.IExperimentationTelemetry,
32+
id: string,
33+
version: string,
34+
globalState: vscode.Memento,
35+
): tas.IExperimentationService {
36+
let targetPopulation: tas.TargetPopulation;
37+
switch (vscode.env.uriScheme) {
38+
case "vscode":
39+
targetPopulation = tas.TargetPopulation.Public;
40+
break;
41+
case "vscode-insiders":
42+
targetPopulation = tas.TargetPopulation.Insiders;
43+
break;
44+
case "vscode-exploration":
45+
targetPopulation = tas.TargetPopulation.Internal;
46+
break;
47+
case "code-oss":
48+
targetPopulation = tas.TargetPopulation.Team;
49+
break;
50+
default:
51+
targetPopulation = tas.TargetPopulation.Public;
52+
break;
53+
}
54+
55+
const experimentationService = tas.getExperimentationService(id, version, targetPopulation, reporter, globalState);
56+
return experimentationService;
57+
}

_extension/src/extension.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ import {
1616
promptUseWorkspaceVersion,
1717
SessionManager,
1818
} from "./session";
19+
20+
import { ExperimentationService } from "./experimentationService";
1921
import { createTelemetryReporter } from "./telemetryReporting";
2022

23+
import assert from "node:assert";
24+
2125
export interface ExtensionAPI {
2226
onLanguageServerInitialized: vscode.Event<void>;
2327
initializeAPIConnection(pipe?: string): Promise<string>;
@@ -29,6 +33,14 @@ export async function activate(context: vscode.ExtensionContext): Promise<Extens
2933
const telemetryReporter = createTelemetryReporter(new VSCodeTelemetryReporter(aiConnectionString));
3034
context.subscriptions.push(telemetryReporter);
3135

36+
const version = context.extension.packageJSON.version;
37+
assert(typeof version === "string");
38+
// Constructing the experimentation service actually sets shared properties
39+
// so that events include context on treatments/flights.
40+
// If we actually need to read treatment variables we would hold onto this instance,
41+
// but for now we just construct it to ensure shared properties are set for telemetry.
42+
void new ExperimentationService(telemetryReporter, context.extension.id, version, context.globalState);
43+
3244
registerEnablementCommands(context, telemetryReporter);
3345

3446
const output = vscode.window.createOutputChannel("typescript-native-preview", { log: true });

_extension/src/telemetryReporting.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { TelemetryReporter as VSCodeTelemetryReporter } from "@vscode/extension-telemetry";
2+
import type { IExperimentationTelemetry } from "vscode-tas-client";
23

34
// As new events are added, update the TelemetryReporter interface below.
45
// This helps ensure that the telemetry events used in the codebase are
@@ -27,21 +28,45 @@ export interface TelemetryReporter {
2728
dispose(): void;
2829
}
2930

30-
export function createTelemetryReporter(vscReporter: VSCodeTelemetryReporter): TelemetryReporter {
31+
export interface ExperimentationTelemetryReporter extends TelemetryReporter, IExperimentationTelemetry {}
32+
33+
// Note:
34+
// This reporter *supports* experimentation telemetry,
35+
// but will only do so when passed to an `ExperimentationService` which
36+
// will set shared properties on this reporter.
37+
export function createTelemetryReporter(vscReporter: VSCodeTelemetryReporter): ExperimentationTelemetryReporter {
38+
let sharedProperties: Record<string, string> = Object.create(null);
39+
3140
return {
41+
// Primary reporting methods for the extension.
3242
sendTelemetryEvent,
3343
sendTelemetryErrorEvent,
3444
sendTelemetryEventUntyped: sendTelemetryEvent,
3545
sendTelemetryErrorEventUntyped: sendTelemetryErrorEvent,
3646

47+
// Required for the experimentation telemetry service interface.
48+
setSharedProperty,
49+
postEvent,
50+
3751
dispose: () => vscReporter.dispose(),
3852
};
3953

54+
function setSharedProperty(key: string, value: string): void {
55+
sharedProperties[key] = value;
56+
}
57+
58+
function postEvent(eventName: string, props: Map<string, string>): void {
59+
const propsAsObj = { ...sharedProperties, ...Object.fromEntries(props) };
60+
vscReporter.sendTelemetryEvent(eventName, propsAsObj);
61+
}
62+
4063
function sendTelemetryEvent(eventName: string, data?: Record<string, string>, measurements?: Record<string, number>): void {
64+
data = { ...sharedProperties, ...data };
4165
vscReporter.sendTelemetryEvent(eventName, data, measurements);
4266
}
4367

4468
function sendTelemetryErrorEvent(eventName: string, data?: Record<string, string>, measurements?: Record<string, number>): void {
69+
data = { ...sharedProperties, ...data };
4570
vscReporter.sendTelemetryErrorEvent(eventName, data, measurements);
4671
}
4772
}

_extension/src/util.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { exec } from "child_process";
2-
import { get } from "http";
31
import * as path from "path";
42
import * as vscode from "vscode";
53

@@ -222,3 +220,8 @@ export function readUnifiedConfig<T>(
222220
if (explicit !== undefined) return explicit;
223221
return vscode.workspace.getConfiguration(fallbackSection, scope).get<T>(fallbackKey, defaultValue);
224222
}
223+
224+
export interface PackageInfo {
225+
name: string;
226+
version: string;
227+
}

_extension/tsconfig.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
{
22
"compilerOptions": {
3-
"target": "es2023",
4-
"module": "NodeNext",
53
"rootDir": "./src",
64
"outDir": "./dist",
7-
"esModuleInterop": true,
8-
"strict": true,
9-
"skipLibCheck": true,
10-
"resolveJsonModule": true,
11-
"sourceMap": true,
125
"noEmit": true,
13-
"types": ["node"]
6+
7+
"target": "es2023",
8+
"module": "preserve",
9+
"moduleResolution": "bundler",
10+
"types": ["node"],
11+
12+
"strict": true,
13+
"noImplicitOverride": true
1414
},
1515
"include": ["./src/**/*"]
1616
}

package-lock.json

Lines changed: 20 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)