diff --git a/src/client.ts b/src/client.ts index e862951a8..f854d08a3 100644 --- a/src/client.ts +++ b/src/client.ts @@ -210,6 +210,7 @@ export default class Client implements ClientInterface { }) ); + this.telemetry.serverVersion = await this.getServerVersion(); await this.client.start(); await this.determineFormatter(); @@ -621,6 +622,21 @@ export default class Client implements ClientInterface { }); } + private async getServerVersion(): Promise { + const result = await asyncExec( + `BUNDLE_GEMFILE=${path.join( + this.workingFolder, + "Gemfile" + )} bundle exec ruby -e "require 'ruby-lsp'; print RubyLsp::VERSION"`, + { + cwd: this.workingFolder, + env: this.ruby.env, + } + ); + + return result.stdout; + } + // If the `.git` folder exists and `.git/rebase-merge` or `.git/rebase-apply` exists, then we're in the middle of a // rebase private rebaseInProgress() { @@ -633,7 +649,8 @@ export default class Client implements ClientInterface { ); } - private openLink(link: string) { + private async openLink(link: string) { + await this.telemetry.sendCodeLensEvent("link"); vscode.env.openExternal(vscode.Uri.parse(link)); } } diff --git a/src/extension.ts b/src/extension.ts index f42dec88a..95a6420d1 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -20,7 +20,8 @@ export async function activate(context: vscode.ExtensionContext) { testController = new TestController( context, vscode.workspace.workspaceFolders![0].uri.fsPath, - ruby + ruby, + telemetry ); client = new Client(context, telemetry, ruby, testController); diff --git a/src/telemetry.ts b/src/telemetry.ts index abc21a50d..0be843cd9 100644 --- a/src/telemetry.ts +++ b/src/telemetry.ts @@ -19,7 +19,12 @@ export interface ConfigurationEvent { value: string; } -export type TelemetryEvent = RequestEvent | ConfigurationEvent; +export interface CodeLensEvent { + type: "test" | "debug" | "test_in_terminal" | "link"; + lspVersion: string; +} + +export type TelemetryEvent = RequestEvent | ConfigurationEvent | CodeLensEvent; const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000; @@ -35,6 +40,7 @@ class DevelopmentApi implements TelemetryApi { } export class Telemetry { + public serverVersion?: string; private api?: TelemetryApi; private context: vscode.ExtensionContext; @@ -103,6 +109,10 @@ export class Telemetry { ); } + async sendCodeLensEvent(type: CodeLensEvent["type"]) { + await this.sendEvent({ type, lspVersion: this.serverVersion! }); + } + private async initialize(): Promise { try { if (!this.api) { diff --git a/src/test/suite/telemetry.test.ts b/src/test/suite/telemetry.test.ts index 2119d6724..c7a86b029 100644 --- a/src/test/suite/telemetry.test.ts +++ b/src/test/suite/telemetry.test.ts @@ -7,6 +7,7 @@ import { TelemetryApi, TelemetryEvent, ConfigurationEvent, + CodeLensEvent, } from "../../telemetry"; class FakeApi implements TelemetryApi { @@ -98,4 +99,25 @@ suite("Telemetry", () => { assert.strictEqual(typeof (event as ConfigurationEvent).value, "string"); }); }); + + test("Send code lens event includes configured server version", 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 + ); + + telemetry.serverVersion = "1.0.0"; + await telemetry.sendCodeLensEvent("test"); + + const codeLensEvent = api.sentEvents[0] as CodeLensEvent; + assert.strictEqual(codeLensEvent.type, "test"); + assert.strictEqual(codeLensEvent.lspVersion, "1.0.0"); + }); }); diff --git a/src/testController.ts b/src/testController.ts index 7e8dfa07b..33fe93085 100644 --- a/src/testController.ts +++ b/src/testController.ts @@ -6,6 +6,7 @@ import { CodeLens } from "vscode-languageclient/node"; import { Ruby } from "./ruby"; import { Command } from "./status"; +import { Telemetry } from "./telemetry"; const asyncExec = promisify(exec); @@ -18,14 +19,17 @@ export class TestController { private workingFolder: string; private terminal: vscode.Terminal | undefined; private ruby: Ruby; + private telemetry: Telemetry; constructor( context: vscode.ExtensionContext, workingFolder: string, - ruby: Ruby + ruby: Ruby, + telemetry: Telemetry ) { this.workingFolder = workingFolder; this.ruby = ruby; + this.telemetry = telemetry; this.testController = vscode.tests.createTestController( "rubyTests", @@ -141,7 +145,13 @@ export class TestController { }); } - private runTestInTerminal(_path: string, _name: string, command: string) { + private async runTestInTerminal( + _path: string, + _name: string, + command: string + ) { + await this.telemetry.sendCodeLensEvent("test_in_terminal"); + if (this.terminal === undefined) { this.terminal = vscode.window.createTerminal({ name: "Run test" }); } @@ -153,6 +163,7 @@ export class TestController { request: vscode.TestRunRequest, _token: vscode.CancellationToken ) { + await this.telemetry.sendCodeLensEvent("debug"); const run = this.testController.createTestRun(request, undefined, true); const test = request.include![0]; @@ -166,6 +177,7 @@ export class TestController { request: vscode.TestRunRequest, token: vscode.CancellationToken ) { + await this.telemetry.sendCodeLensEvent("test"); const run = this.testController.createTestRun(request, undefined, true); const queue: vscode.TestItem[] = [];