Skip to content
3 changes: 2 additions & 1 deletion _extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@
},
"dependencies": {
"@vscode/extension-telemetry": "^1.5.1",
"vscode-languageclient": "^10.0.0-next.21"
"vscode-languageclient": "^10.0.0-next.21",
"vscode-tas-client": "0.1.84"
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locked on 0.1.84 because of microsoft/tas-client#95

},
"devDependencies": {
"@types/vscode": "~1.110.0",
Expand Down
1 change: 0 additions & 1 deletion _extension/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type {
Position,
} from "vscode-languageclient";

import { Client } from "./client";
Comment thread
DanielRosenwasser marked this conversation as resolved.
import type * as tr from "./telemetryReporting";
import { restartExtHostOnChangeIfNeeded } from "./util";

Expand Down
57 changes: 57 additions & 0 deletions _extension/src/experimentationService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as vscode from "vscode";
import * as tas from "vscode-tas-client";

interface ExperimentTypes {
// Experiment variables go here.
suggestNativePreview: boolean;
}

export class ExperimentationService {
private readonly _experimentationService: tas.IExperimentationService;
private readonly _telemetryReporter: tas.IExperimentationTelemetry;

constructor(telemetryReporter: tas.IExperimentationTelemetry, id: string, version: string, globalState: vscode.Memento) {
this._telemetryReporter = telemetryReporter;
this._experimentationService = createTasExperimentationService(this._telemetryReporter, id, version, globalState);
}

public async getTreatmentVariable<K extends keyof ExperimentTypes>(name: K, defaultValue: ExperimentTypes[K]): Promise<ExperimentTypes[K]> {
const experimentationService = this._experimentationService;
try {
const treatmentVariable = await experimentationService.getTreatmentVariableAsync("vscode", name, /*checkCache*/ true) as ExperimentTypes[K];
return treatmentVariable ?? defaultValue;
}
catch {
return defaultValue;
}
Comment on lines +18 to +26
}
}

function createTasExperimentationService(
reporter: tas.IExperimentationTelemetry,
id: string,
version: string,
globalState: vscode.Memento,
): tas.IExperimentationService {
let targetPopulation: tas.TargetPopulation;
switch (vscode.env.uriScheme) {
case "vscode":
targetPopulation = tas.TargetPopulation.Public;
break;
case "vscode-insiders":
targetPopulation = tas.TargetPopulation.Insiders;
break;
case "vscode-exploration":
targetPopulation = tas.TargetPopulation.Internal;
break;
case "code-oss":
targetPopulation = tas.TargetPopulation.Team;
break;
default:
targetPopulation = tas.TargetPopulation.Public;
break;
}

const experimentationService = tas.getExperimentationService(id, version, targetPopulation, reporter, globalState);
return experimentationService;
}
12 changes: 12 additions & 0 deletions _extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ import {
promptUseWorkspaceVersion,
SessionManager,
} from "./session";

import { ExperimentationService } from "./experimentationService";
import { createTelemetryReporter } from "./telemetryReporting";

import assert from "node:assert";

export interface ExtensionAPI {
onLanguageServerInitialized: vscode.Event<void>;
initializeAPIConnection(pipe?: string): Promise<string>;
Expand All @@ -29,6 +33,14 @@ export async function activate(context: vscode.ExtensionContext): Promise<Extens
const telemetryReporter = createTelemetryReporter(new VSCodeTelemetryReporter(aiConnectionString));
context.subscriptions.push(telemetryReporter);

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

registerEnablementCommands(context, telemetryReporter);

const output = vscode.window.createOutputChannel("typescript-native-preview", { log: true });
Expand Down
27 changes: 26 additions & 1 deletion _extension/src/telemetryReporting.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { TelemetryReporter as VSCodeTelemetryReporter } from "@vscode/extension-telemetry";
import type { IExperimentationTelemetry } from "vscode-tas-client";

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

export function createTelemetryReporter(vscReporter: VSCodeTelemetryReporter): TelemetryReporter {
export interface ExperimentationTelemetryReporter extends TelemetryReporter, IExperimentationTelemetry {}

// Note:
// This reporter *supports* experimentation telemetry,
// but will only do so when passed to an `ExperimentationService` which
// will set shared properties on this reporter.
export function createTelemetryReporter(vscReporter: VSCodeTelemetryReporter): ExperimentationTelemetryReporter {
let sharedProperties: Record<string, string> = Object.create(null);

return {
// Primary reporting methods for the extension.
sendTelemetryEvent,
sendTelemetryErrorEvent,
sendTelemetryEventUntyped: sendTelemetryEvent,
sendTelemetryErrorEventUntyped: sendTelemetryErrorEvent,

// Required for the experimentation telemetry service interface.
setSharedProperty,
postEvent,

dispose: () => vscReporter.dispose(),
};

function setSharedProperty(key: string, value: string): void {
sharedProperties[key] = value;
}

function postEvent(eventName: string, props: Map<string, string>): void {
const propsAsObj = { ...sharedProperties, ...Object.fromEntries(props) };
vscReporter.sendTelemetryEvent(eventName, propsAsObj);
}

function sendTelemetryEvent(eventName: string, data?: Record<string, string>, measurements?: Record<string, number>): void {
data = { ...sharedProperties, ...data };
vscReporter.sendTelemetryEvent(eventName, data, measurements);
}

function sendTelemetryErrorEvent(eventName: string, data?: Record<string, string>, measurements?: Record<string, number>): void {
data = { ...sharedProperties, ...data };
vscReporter.sendTelemetryErrorEvent(eventName, data, measurements);
}
}
Expand Down
7 changes: 5 additions & 2 deletions _extension/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { exec } from "child_process";
import { get } from "http";
import * as path from "path";
import * as vscode from "vscode";

Expand Down Expand Up @@ -222,3 +220,8 @@ export function readUnifiedConfig<T>(
if (explicit !== undefined) return explicit;
return vscode.workspace.getConfiguration(fallbackSection, scope).get<T>(fallbackKey, defaultValue);
}

export interface PackageInfo {
name: string;
version: string;
}
16 changes: 8 additions & 8 deletions _extension/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"compilerOptions": {
"target": "es2023",
"module": "NodeNext",
"rootDir": "./src",
"outDir": "./dist",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"sourceMap": true,
"noEmit": true,
"types": ["node"]

"target": "es2023",
"module": "preserve",
"moduleResolution": "bundler",
"types": ["node"],

"strict": true,
"noImplicitOverride": true
},
"include": ["./src/**/*"]
}
21 changes: 20 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading