Skip to content

Commit 72a3e8d

Browse files
authored
Merge pull request #2307 from github/aeisenberg/cli-version-telemetry
Add the CLI version to telemetry events
2 parents 10adbd6 + 18f2d79 commit 72a3e8d

File tree

4 files changed

+139
-11
lines changed

4 files changed

+139
-11
lines changed

extensions/ql-vscode/src/cli.ts

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ export type OnLineCallback = (
171171
line: string,
172172
) => Promise<string | undefined> | string | undefined;
173173

174+
type VersionChangedListener = (newVersion: SemVer | undefined) => void;
175+
174176
/**
175177
* This class manages a cli server started by `codeql execute cli-server` to
176178
* run commands without the overhead of starting a new java
@@ -188,7 +190,9 @@ export class CodeQLCliServer implements Disposable {
188190
nullBuffer: Buffer;
189191

190192
/** Version of current cli, lazily computed by the `getVersion()` method */
191-
private _version: Promise<SemVer> | undefined;
193+
private _version: SemVer | undefined;
194+
195+
private _versionChangedListeners: VersionChangedListener[] = [];
192196

193197
/**
194198
* The languages supported by the current version of the CLI, computed by `getSupportedLanguages()`.
@@ -1417,15 +1421,36 @@ export class CodeQLCliServer implements Disposable {
14171421

14181422
public async getVersion() {
14191423
if (!this._version) {
1420-
this._version = this.refreshVersion();
1421-
// this._version is only undefined upon config change, so we reset CLI-based context key only when necessary.
1422-
await this.app.commands.execute(
1423-
"setContext",
1424-
"codeql.supportsEvalLog",
1425-
await this.cliConstraints.supportsPerQueryEvalLog(),
1426-
);
1424+
try {
1425+
const newVersion = await this.refreshVersion();
1426+
this._version = newVersion;
1427+
this._versionChangedListeners.forEach((listener) =>
1428+
listener(newVersion),
1429+
);
1430+
1431+
// this._version is only undefined upon config change, so we reset CLI-based context key only when necessary.
1432+
await this.app.commands.execute(
1433+
"setContext",
1434+
"codeql.supportsEvalLog",
1435+
newVersion.compare(
1436+
CliVersionConstraint.CLI_VERSION_WITH_PER_QUERY_EVAL_LOG,
1437+
) >= 0,
1438+
);
1439+
} catch (e) {
1440+
this._versionChangedListeners.forEach((listener) =>
1441+
listener(undefined),
1442+
);
1443+
throw e;
1444+
}
1445+
}
1446+
return this._version;
1447+
}
1448+
1449+
public addVersionChangedListener(listener: VersionChangedListener) {
1450+
if (this._version) {
1451+
listener(this._version);
14271452
}
1428-
return await this._version;
1453+
this._versionChangedListeners.push(listener);
14291454
}
14301455

14311456
private async refreshVersion() {

extensions/ql-vscode/src/extension.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ export async function activate(
317317

318318
const distributionConfigListener = new DistributionConfigListener();
319319
await initializeLogging(ctx);
320-
await initializeTelemetry(extension, ctx);
320+
const telemetryListener = await initializeTelemetry(extension, ctx);
321321
addUnhandledRejectionListener();
322322
install();
323323

@@ -406,6 +406,9 @@ export async function activate(
406406
variantAnalysisViewSerializer.onExtensionLoaded(
407407
codeQlExtension.variantAnalysisManager,
408408
);
409+
codeQlExtension.cliServer.addVersionChangedListener((ver) => {
410+
telemetryListener.cliVersion = ver;
411+
});
409412
}
410413

411414
return codeQlExtension;

extensions/ql-vscode/src/telemetry.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { extLogger } from "./common";
1919
import { UserCancellationException } from "./progress";
2020
import { showBinaryChoiceWithUrlDialog } from "./helpers";
2121
import { RedactableError } from "./pure/errors";
22+
import { SemVer } from "semver";
2223

2324
// Key is injected at build time through the APP_INSIGHTS_KEY environment variable.
2425
const key = "REPLACE-APP-INSIGHTS-KEY";
@@ -51,11 +52,15 @@ const baseDataPropertiesToRemove = [
5152
"common.vscodesessionid",
5253
];
5354

55+
const NOT_SET_CLI_VERSION = "not-set";
56+
5457
export class TelemetryListener extends ConfigListener {
5558
static relevantSettings = [ENABLE_TELEMETRY, CANARY_FEATURES];
5659

5760
private reporter?: TelemetryReporter;
5861

62+
private cliVersionStr = NOT_SET_CLI_VERSION;
63+
5964
constructor(
6065
private readonly id: string,
6166
private readonly version: string,
@@ -163,6 +168,7 @@ export class TelemetryListener extends ConfigListener {
163168
name,
164169
status,
165170
isCanary: isCanary().toString(),
171+
cliVersion: this.cliVersionStr,
166172
},
167173
{ executionTime },
168174
);
@@ -178,6 +184,7 @@ export class TelemetryListener extends ConfigListener {
178184
{
179185
name,
180186
isCanary: isCanary().toString(),
187+
cliVersion: this.cliVersionStr,
181188
},
182189
{},
183190
);
@@ -193,6 +200,7 @@ export class TelemetryListener extends ConfigListener {
193200

194201
const properties: { [key: string]: string } = {
195202
isCanary: isCanary().toString(),
203+
cliVersion: this.cliVersionStr,
196204
message: error.redactedMessage,
197205
...extraProperties,
198206
};
@@ -241,6 +249,10 @@ export class TelemetryListener extends ConfigListener {
241249
return this.reporter;
242250
}
243251

252+
set cliVersion(version: SemVer | undefined) {
253+
this.cliVersionStr = version ? version.toString() : NOT_SET_CLI_VERSION;
254+
}
255+
244256
private disposeReporter() {
245257
if (this.reporter) {
246258
void this.reporter.dispose();
@@ -265,7 +277,7 @@ export let telemetryListener: TelemetryListener | undefined;
265277
export async function initializeTelemetry(
266278
extension: Extension<any>,
267279
ctx: ExtensionContext,
268-
): Promise<void> {
280+
): Promise<TelemetryListener> {
269281
if (telemetryListener !== undefined) {
270282
throw new Error("Telemetry is already initialized");
271283
}
@@ -279,4 +291,5 @@ export async function initializeTelemetry(
279291
// this is a particular problem during integration tests, which will hang if a modal popup is displayed.
280292
void telemetryListener.initialize();
281293
ctx.subscriptions.push(telemetryListener);
294+
return telemetryListener;
282295
}

extensions/ql-vscode/test/vscode-tests/no-workspace/telemetry.test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ENABLE_TELEMETRY } from "../../../src/config";
1414
import { createMockExtensionContext } from "./index";
1515
import { vscodeGetConfigurationMock } from "../test-config";
1616
import { redactableError } from "../../../src/pure/errors";
17+
import { SemVer } from "semver";
1718

1819
// setting preferences can trigger lots of background activity
1920
// so need to bump up the timeout of this test.
@@ -193,6 +194,7 @@ describe("telemetry reporting", () => {
193194
name: "command-id",
194195
status: "Success",
195196
isCanary,
197+
cliVersion: "not-set",
196198
},
197199
{ executionTime: 1234 },
198200
);
@@ -215,13 +217,59 @@ describe("telemetry reporting", () => {
215217
name: "command-id",
216218
status: "Cancelled",
217219
isCanary,
220+
cliVersion: "not-set",
218221
},
219222
{ executionTime: 1234 },
220223
);
221224

222225
expect(sendTelemetryExceptionSpy).not.toBeCalled();
223226
});
224227

228+
it("should send a command usage event with a cli version", async () => {
229+
await telemetryListener.initialize();
230+
telemetryListener.cliVersion = new SemVer("1.2.3");
231+
232+
telemetryListener.sendCommandUsage(
233+
"command-id",
234+
1234,
235+
new UserCancellationException(),
236+
);
237+
238+
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
239+
"command-usage",
240+
{
241+
name: "command-id",
242+
status: "Cancelled",
243+
isCanary,
244+
cliVersion: "1.2.3",
245+
},
246+
{ executionTime: 1234 },
247+
);
248+
249+
expect(sendTelemetryExceptionSpy).not.toBeCalled();
250+
251+
// Verify that if the cli version is not set, then the telemetry falls back to "not-set"
252+
sendTelemetryEventSpy.mockClear();
253+
telemetryListener.cliVersion = undefined;
254+
255+
telemetryListener.sendCommandUsage(
256+
"command-id",
257+
5678,
258+
new UserCancellationException(),
259+
);
260+
261+
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
262+
"command-usage",
263+
{
264+
name: "command-id",
265+
status: "Cancelled",
266+
isCanary,
267+
cliVersion: "not-set",
268+
},
269+
{ executionTime: 5678 },
270+
);
271+
});
272+
225273
it("should avoid sending an event when telemetry is disabled", async () => {
226274
await telemetryListener.initialize();
227275
await enableTelemetry("codeQL.telemetry", false);
@@ -246,6 +294,7 @@ describe("telemetry reporting", () => {
246294
name: "command-id",
247295
status: "Success",
248296
isCanary,
297+
cliVersion: "not-set",
249298
},
250299
{ executionTime: 1234 },
251300
);
@@ -402,6 +451,24 @@ describe("telemetry reporting", () => {
402451
{
403452
name: "test",
404453
isCanary,
454+
cliVersion: "not-set",
455+
},
456+
{},
457+
);
458+
});
459+
460+
it("should send a ui-interaction telementry event with a cli version", async () => {
461+
await telemetryListener.initialize();
462+
463+
telemetryListener.cliVersion = new SemVer("1.2.3");
464+
telemetryListener.sendUIInteraction("test");
465+
466+
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
467+
"ui-interaction",
468+
{
469+
name: "test",
470+
isCanary,
471+
cliVersion: "1.2.3",
405472
},
406473
{},
407474
);
@@ -418,6 +485,25 @@ describe("telemetry reporting", () => {
418485
message: "test",
419486
isCanary,
420487
stack: expect.any(String),
488+
cliVersion: "not-set",
489+
},
490+
{},
491+
);
492+
});
493+
494+
it("should send an error telementry event with a cli version", async () => {
495+
await telemetryListener.initialize();
496+
telemetryListener.cliVersion = new SemVer("1.2.3");
497+
498+
telemetryListener.sendError(redactableError`test`);
499+
500+
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
501+
"error",
502+
{
503+
message: "test",
504+
isCanary,
505+
stack: expect.any(String),
506+
cliVersion: "1.2.3",
421507
},
422508
{},
423509
);
@@ -436,6 +522,7 @@ describe("telemetry reporting", () => {
436522
message:
437523
"test message with secret information: [REDACTED] and more [REDACTED] parts",
438524
isCanary,
525+
cliVersion: "not-set",
439526
stack: expect.any(String),
440527
},
441528
{},

0 commit comments

Comments
 (0)