diff --git a/src/extension.ts b/src/extension.ts index d35d88a28..f42dec88a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,6 +15,8 @@ export async function activate(context: vscode.ExtensionContext) { await ruby.activateRuby(); const telemetry = new Telemetry(context); + await telemetry.sendConfigurationEvents(); + testController = new TestController( context, vscode.workspace.workspaceFolders![0].uri.fsPath, diff --git a/src/telemetry.ts b/src/telemetry.ts index fe89cbe75..abc21a50d 100644 --- a/src/telemetry.ts +++ b/src/telemetry.ts @@ -1,6 +1,6 @@ import * as vscode from "vscode"; -export interface TelemetryEvent { +interface RequestEvent { request: string; requestTime: number; lspVersion: string; @@ -13,6 +13,16 @@ export interface TelemetryEvent { yjitEnabled: boolean; } +export interface ConfigurationEvent { + namespace: string; + field: string; + value: string; +} + +export type TelemetryEvent = RequestEvent | ConfigurationEvent; + +const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000; + export interface TelemetryApi { sendEvent(event: TelemetryEvent): Promise; } @@ -26,8 +36,11 @@ class DevelopmentApi implements TelemetryApi { export class Telemetry { private api?: TelemetryApi; + private context: vscode.ExtensionContext; constructor(context: vscode.ExtensionContext, api?: TelemetryApi) { + this.context = context; + if (context.extensionMode === vscode.ExtensionMode.Development && !api) { this.api = new DevelopmentApi(); } else { @@ -37,8 +50,57 @@ export class Telemetry { async sendEvent(event: TelemetryEvent) { if (await this.initialize()) { - return this.api!.sendEvent(event); + this.api!.sendEvent(event); + } + } + + async sendConfigurationEvents() { + const lastConfigurationTelemetry: number | undefined = + this.context.globalState.get("rubyLsp.lastConfigurationTelemetry"); + + if ( + lastConfigurationTelemetry && + Date.now() - lastConfigurationTelemetry <= ONE_DAY_IN_MS + ) { + return; } + + const promises: Promise[] = [ + { namespace: "workbench", field: "colorTheme" }, + { namespace: "rubyLsp", field: "enableExperimentalFeatures" }, + { namespace: "rubyLsp", field: "yjit" }, + { namespace: "rubyLsp", field: "rubyVersionManager" }, + { namespace: "rubyLsp", field: "formatter" }, + ].map(({ namespace, field }) => { + return this.sendEvent({ + namespace, + field, + value: ( + vscode.workspace.getConfiguration(namespace).get(field) ?? "" + ).toString(), + }); + }); + + const enabledFeatures = vscode.workspace + .getConfiguration("rubyLsp") + .get("enabledFeatures")!; + + Object.entries(enabledFeatures).forEach(([field, value]) => { + promises.push( + this.sendEvent({ + namespace: "rubyLsp.enabledFeatures", + field, + value: value.toString(), + }) + ); + }); + + await Promise.all(promises); + + this.context.globalState.update( + "rubyLsp.lastConfigurationTelemetry", + Date.now() + ); } private async initialize(): Promise { diff --git a/src/test/suite/telemetry.test.ts b/src/test/suite/telemetry.test.ts index e37b6a793..2119d6724 100644 --- a/src/test/suite/telemetry.test.ts +++ b/src/test/suite/telemetry.test.ts @@ -2,7 +2,12 @@ import * as assert from "assert"; import * as vscode from "vscode"; -import { Telemetry, TelemetryApi, TelemetryEvent } from "../../telemetry"; +import { + Telemetry, + TelemetryApi, + TelemetryEvent, + ConfigurationEvent, +} from "../../telemetry"; class FakeApi implements TelemetryApi { public sentEvents: TelemetryEvent[]; @@ -65,4 +70,32 @@ suite("Telemetry", () => { await telemetry.sendEvent(event); assert.strictEqual(api.sentEvents[0], event); }); + + test("Send configuration events emits telemetry for relevant configurations", async () => { + const api = new FakeApi(); + const telemetry = new Telemetry( + { + extensionMode: vscode.ExtensionMode.Production, + globalState: { + get: () => undefined, + update: () => Promise.resolve(), + } as unknown, + } as vscode.ExtensionContext, + api + ); + + await telemetry.sendConfigurationEvents(); + const featureConfigurations = vscode.workspace + .getConfiguration("rubyLsp") + .get("enabledFeatures")!; + + const expectedNumberOfEvents = + 5 + Object.keys(featureConfigurations).length; + + assert.strictEqual(api.sentEvents.length, expectedNumberOfEvents); + + api.sentEvents.forEach((event) => { + assert.strictEqual(typeof (event as ConfigurationEvent).value, "string"); + }); + }); });