Remove extension telemetry setting and use VS Code setting instead

This commit is contained in:
Koen Vlaswinkel
2024-11-06 16:32:34 +01:00
parent b1a15d8340
commit 52beaeded2
8 changed files with 22 additions and 566 deletions

View File

@@ -497,16 +497,6 @@
"title": "Telemetry",
"order": 11,
"properties": {
"codeQL.telemetry.enableTelemetry": {
"type": "boolean",
"default": false,
"scope": "application",
"markdownDescription": "Specifies whether to send CodeQL usage telemetry. This setting AND the one of the global telemetry settings (`#telemetry.enableTelemetry#` or `#telemetry.telemetryLevel#`) must be enabled for telemetry to be sent to GitHub. For more information, see the [telemetry documentation](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code)",
"tags": [
"telemetry",
"usesOnlineServices"
]
},
"codeQL.telemetry.logTelemetry": {
"type": "boolean",
"default": false,

View File

@@ -1,4 +1,4 @@
import { env, Uri, window } from "vscode";
import { window } from "vscode";
/**
* Opens a modal dialog for the user to make a yes/no choice.
@@ -34,50 +34,6 @@ export async function showBinaryChoiceDialog(
return chosenItem?.title === yesItem.title;
}
/**
* Opens a modal dialog for the user to make a yes/no choice.
*
* @param message The message to show.
* @param modal If true (the default), show a modal dialog box, otherwise dialog is non-modal and can
* be closed even if the user does not make a choice.
*
* @return
* `true` if the user clicks 'Yes',
* `false` if the user clicks 'No' or cancels the dialog,
* `undefined` if the dialog is closed without the user making a choice.
*/
export async function showBinaryChoiceWithUrlDialog(
message: string,
url: string,
): Promise<boolean | undefined> {
const urlItem = { title: "More Information", isCloseAffordance: false };
const yesItem = { title: "Yes", isCloseAffordance: false };
const noItem = { title: "No", isCloseAffordance: true };
let chosenItem;
// Keep the dialog open as long as the user is clicking the 'more information' option.
// To prevent an infinite loop, if the user clicks 'more information' 5 times, close the dialog and return cancelled
let count = 0;
do {
chosenItem = await window.showInformationMessage(
message,
{ modal: true },
urlItem,
yesItem,
noItem,
);
if (chosenItem === urlItem) {
await env.openExternal(Uri.parse(url, true));
}
count++;
} while (chosenItem === urlItem && count < 5);
if (!chosenItem || chosenItem.title === urlItem.title) {
return undefined;
}
return chosenItem.title === yesItem.title;
}
/**
* Show an information message with a customisable action.
* @param message The message to show.

View File

@@ -1,26 +1,14 @@
import type {
Extension,
ExtensionContext,
ConfigurationChangeEvent,
} from "vscode";
import { ConfigurationTarget, env } from "vscode";
import type { Extension, ExtensionContext } from "vscode";
import TelemetryReporter from "vscode-extension-telemetry";
import {
ConfigListener,
CANARY_FEATURES,
ENABLE_TELEMETRY,
LOG_TELEMETRY,
isIntegrationTestMode,
isCanary,
} from "../../config";
import { LOG_TELEMETRY, isCanary } from "../../config";
import type { TelemetryClient } from "applicationinsights";
import { extLogger } from "../logging/vscode";
import { UserCancellationException } from "./progress";
import { showBinaryChoiceWithUrlDialog } from "./dialog";
import type { RedactableError } from "../errors";
import type { SemVer } from "semver";
import type { AppTelemetry } from "../telemetry";
import type { EnvelopeTelemetry } from "applicationinsights/out/Declarations/Contracts";
import type { Disposable } from "../disposable-object";
// Key is injected at build time through the APP_INSIGHTS_KEY environment variable.
const key = "REPLACE-APP-INSIGHTS-KEY";
@@ -55,80 +43,25 @@ const baseDataPropertiesToRemove = [
const NOT_SET_CLI_VERSION = "not-set";
export class ExtensionTelemetryListener
extends ConfigListener
implements AppTelemetry
{
private reporter?: TelemetryReporter;
export class ExtensionTelemetryListener implements AppTelemetry, Disposable {
private readonly reporter: TelemetryReporter;
private cliVersionStr = NOT_SET_CLI_VERSION;
constructor(
private readonly id: string,
private readonly version: string,
private readonly key: string,
private readonly ctx: ExtensionContext,
) {
super();
env.onDidChangeTelemetryEnabled(async () => {
await this.initialize();
});
}
/**
* This function handles changes to relevant configuration elements. There are 2 configuration
* ids that this function cares about:
*
* * `codeQL.telemetry.enableTelemetry`: If this one has changed, then we need to re-initialize
* the reporter and the reporter may wind up being removed.
* * `codeQL.canary`: A change here could possibly re-trigger a dialog popup.
*
* Note that the global telemetry setting also gate-keeps whether or not to send telemetry events
* to Application Insights. However, this gatekeeping happens inside of the vscode-extension-telemetry
* package. So, this does not need to be handled here.
*
* @param e the configuration change event
*/
async handleDidChangeConfiguration(
e: ConfigurationChangeEvent,
): Promise<void> {
if (e.affectsConfiguration(ENABLE_TELEMETRY.qualifiedName)) {
await this.initialize();
}
// Re-request telemetry so that users can see the dialog again.
// Re-request if codeQL.canary is being set to `true` and telemetry
// is not currently enabled.
if (
e.affectsConfiguration(CANARY_FEATURES.qualifiedName) &&
CANARY_FEATURES.getValue() &&
!ENABLE_TELEMETRY.getValue()
) {
await this.setTelemetryRequested(false);
await this.requestTelemetryPermission();
}
}
async initialize() {
await this.requestTelemetryPermission();
this.disposeReporter();
if (ENABLE_TELEMETRY.getValue<boolean>()) {
this.createReporter();
}
}
private createReporter() {
constructor(id: string, version: string, key: string) {
// We can always initialize this and send events using it because the vscode-extension-telemetry will check
// whether the `telemetry.telemetryLevel` setting is enabled.
this.reporter = new TelemetryReporter(
this.id,
this.version,
this.key,
id,
version,
key,
/* anonymize stack traces */ true,
);
this.push(this.reporter);
this.addTelemetryProcessor();
}
private addTelemetryProcessor() {
// The appInsightsClient field is private but we want to access it anyway
const client = this.reporter["appInsightsClient"] as TelemetryClient;
if (client) {
@@ -151,14 +84,10 @@ export class ExtensionTelemetryListener
}
dispose() {
super.dispose();
void this.reporter?.dispose();
void this.reporter.dispose();
}
sendCommandUsage(name: string, executionTime: number, error?: Error): void {
if (!this.reporter) {
return;
}
const status = !error
? CommandCompletion.Success
: error instanceof UserCancellationException
@@ -178,10 +107,6 @@ export class ExtensionTelemetryListener
}
sendUIInteraction(name: string): void {
if (!this.reporter) {
return;
}
this.reporter.sendTelemetryEvent(
"ui-interaction",
{
@@ -197,10 +122,6 @@ export class ExtensionTelemetryListener
error: RedactableError,
extraProperties?: { [key: string]: string },
): void {
if (!this.reporter) {
return;
}
const properties: { [key: string]: string } = {
isCanary: isCanary().toString(),
cliVersion: this.cliVersionStr,
@@ -215,10 +136,6 @@ export class ExtensionTelemetryListener
}
sendConfigInformation(config: Record<string, string>): void {
if (!this.reporter) {
return;
}
this.reporter.sendTelemetryEvent(
"config",
{
@@ -230,37 +147,6 @@ export class ExtensionTelemetryListener
);
}
/**
* Displays a popup asking the user if they want to enable telemetry
* for this extension.
*/
async requestTelemetryPermission() {
if (!this.wasTelemetryRequested()) {
// if global telemetry is disabled, avoid showing the dialog or making any changes
let result = undefined;
if (
env.isTelemetryEnabled &&
// Avoid showing the dialog if we are in integration test mode.
!isIntegrationTestMode()
) {
// Extension won't start until this completes.
result = await showBinaryChoiceWithUrlDialog(
"Does the CodeQL Extension by GitHub have your permission to collect usage data and metrics to help us improve CodeQL for VSCode?",
"https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code",
);
}
if (result !== undefined) {
await Promise.all([
this.setTelemetryRequested(true),
ENABLE_TELEMETRY.updateValue<boolean>(
result,
ConfigurationTarget.Global,
),
]);
}
}
}
/**
* Exposed for testing
*/
@@ -271,21 +157,6 @@ export class ExtensionTelemetryListener
set cliVersion(version: SemVer | undefined) {
this.cliVersionStr = version ? version.toString() : NOT_SET_CLI_VERSION;
}
private disposeReporter() {
if (this.reporter) {
void this.reporter.dispose();
this.reporter = undefined;
}
}
private wasTelemetryRequested(): boolean {
return !!this.ctx.globalState.get<boolean>("telemetry-request-viewed");
}
private async setTelemetryRequested(newValue: boolean): Promise<void> {
await this.ctx.globalState.update("telemetry-request-viewed", newValue);
}
}
/**
@@ -305,11 +176,7 @@ export async function initializeTelemetry(
extension.id,
extension.packageJSON.version,
key,
ctx,
);
// do not await initialization, since doing so will sometimes cause a modal popup.
// this is a particular problem during integration tests, which will hang if a modal popup is displayed.
void telemetryListener.initialize();
ctx.subscriptions.push(telemetryListener);
return telemetryListener;
}

View File

@@ -165,10 +165,6 @@ const ROOT_SETTING = new Setting("codeQL");
const TELEMETRY_SETTING = new Setting("telemetry", ROOT_SETTING);
export const LOG_TELEMETRY = new Setting("logTelemetry", TELEMETRY_SETTING);
export const ENABLE_TELEMETRY = new Setting(
"enableTelemetry",
TELEMETRY_SETTING,
);
// Distribution configuration
const DISTRIBUTION_SETTING = new Setting("cli", ROOT_SETTING);

View File

@@ -1,6 +1,5 @@
{
"workbench.startupEditor": "none",
"security.workspace.trust.enabled": false,
"codeQL.cli.executablePath": "/opt/codeql/codeql",
"codeQL.telemetry.enableTelemetry": false
"codeQL.cli.executablePath": "/opt/codeql/codeql"
}

View File

@@ -1,7 +1,6 @@
import { window } from "vscode";
import {
showBinaryChoiceDialog,
showBinaryChoiceWithUrlDialog,
showInformationMessageWithAction,
showNeverAskAgainDialog,
} from "../../../../../src/common/vscode/dialog";
@@ -68,57 +67,6 @@ describe("showInformationMessageWithAction", () => {
});
});
describe("showBinaryChoiceWithUrlDialog", () => {
let showInformationMessageSpy: jest.SpiedFunction<
typeof window.showInformationMessage
>;
beforeEach(() => {
showInformationMessageSpy = jest
.spyOn(window, "showInformationMessage")
.mockResolvedValue(undefined);
});
const resolveArg =
(index: number) =>
(...args: any[]) =>
Promise.resolve(args[index]);
it("should show a binary choice dialog with a url and return `yes`", async () => {
// pretend user clicks on the url twice and then clicks 'yes'
showInformationMessageSpy
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(3));
const val = await showBinaryChoiceWithUrlDialog("xxx", "invalid:url");
expect(val).toBe(true);
});
it("should show a binary choice dialog with a url and return `no`", async () => {
// pretend user clicks on the url twice and then clicks 'no'
showInformationMessageSpy
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(4));
const val = await showBinaryChoiceWithUrlDialog("xxx", "invalid:url");
expect(val).toBe(false);
});
it("should show a binary choice dialog and exit after clcking `more info` 5 times", async () => {
// pretend user clicks on the url twice and then clicks 'no'
showInformationMessageSpy
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(2))
.mockImplementation(resolveArg(2));
const val = await showBinaryChoiceWithUrlDialog("xxx", "invalid:url");
// No choice was made
expect(val).toBeUndefined();
expect(showInformationMessageSpy).toHaveBeenCalledTimes(5);
});
});
describe("showNeverAskAgainDialog", () => {
let showInformationMessageSpy: jest.SpiedFunction<
typeof window.showInformationMessage

View File

@@ -3,9 +3,7 @@ import type { ExtensionContext } from "vscode";
export function createMockExtensionContext(): ExtensionContext {
return {
globalState: {
_state: {
"telemetry-request-viewed": true,
} as Record<string, any>,
_state: {} as Record<string, any>,
get(key: string) {
return this._state[key];
},

View File

@@ -1,14 +1,10 @@
import TelemetryReporter from "vscode-extension-telemetry";
import type { ExtensionContext } from "vscode";
import { workspace, ConfigurationTarget, window, env } from "vscode";
import { workspace, env } from "vscode";
import {
ExtensionTelemetryListener,
telemetryListener as globalTelemetryListener,
} from "../../../src/common/vscode/telemetry";
import { UserCancellationException } from "../../../src/common/vscode/progress";
import { ENABLE_TELEMETRY } from "../../../src/config";
import { createMockExtensionContext } from "./index";
import { vscodeGetConfigurationMock } from "../test-config";
import { redactableError } from "../../../src/common/errors";
import { SemVer } from "semver";
@@ -17,10 +13,7 @@ import { SemVer } from "semver";
jest.setTimeout(10000);
describe("telemetry reporting", () => {
let originalTelemetryExtension: boolean | undefined;
let originalTelemetryGlobal: string | undefined;
let isCanary: string;
let ctx: ExtensionContext;
let telemetryListener: ExtensionTelemetryListener;
let sendTelemetryEventSpy: jest.SpiedFunction<
@@ -29,22 +22,8 @@ describe("telemetry reporting", () => {
let sendTelemetryErrorEventSpy: jest.SpiedFunction<
typeof TelemetryReporter.prototype.sendTelemetryErrorEvent
>;
let disposeSpy: jest.SpiedFunction<
typeof TelemetryReporter.prototype.dispose
>;
let isTelemetryEnabledSpy: jest.SpyInstance<
typeof env.isTelemetryEnabled,
[]
>;
let showInformationMessageSpy: jest.SpiedFunction<
typeof window.showInformationMessage
>;
beforeEach(async () => {
vscodeGetConfigurationMock.mockRestore();
try {
// in case a previous test has accidentally activated this extension,
// need to disable it first.
@@ -52,44 +31,24 @@ describe("telemetry reporting", () => {
// specified in the package.json.
globalTelemetryListener?.dispose();
ctx = createMockExtensionContext();
sendTelemetryEventSpy = jest
.spyOn(TelemetryReporter.prototype, "sendTelemetryEvent")
.mockReturnValue(undefined);
sendTelemetryErrorEventSpy = jest
.spyOn(TelemetryReporter.prototype, "sendTelemetryErrorEvent")
.mockReturnValue(undefined);
disposeSpy = jest
.spyOn(TelemetryReporter.prototype, "dispose")
.mockResolvedValue(undefined);
showInformationMessageSpy = jest
.spyOn(window, "showInformationMessage")
.mockResolvedValue(undefined);
originalTelemetryExtension = workspace
.getConfiguration()
.get<boolean>("codeQL.telemetry.enableTelemetry");
originalTelemetryGlobal = workspace
.getConfiguration()
.get<string>("telemetry.telemetryLevel");
isCanary = (!!workspace
.getConfiguration()
.get<boolean>("codeQL.canary")).toString();
// each test will default to telemetry being enabled
isTelemetryEnabledSpy = jest
.spyOn(env, "isTelemetryEnabled", "get")
.mockReturnValue(true);
await setTelemetryLevel("telemetry", "all");
await enableTelemetry("codeQL.telemetry", true);
jest.spyOn(env, "isTelemetryEnabled", "get").mockReturnValue(true);
telemetryListener = new ExtensionTelemetryListener(
"my-id",
"1.2.3",
"fake-key",
ctx,
);
await wait(100);
} catch (e) {
@@ -99,18 +58,9 @@ describe("telemetry reporting", () => {
afterEach(async () => {
telemetryListener?.dispose();
// await wait(100);
try {
await setTelemetryLevel("telemetry", originalTelemetryGlobal);
await enableTelemetry("codeQL.telemetry", originalTelemetryExtension);
} catch (e) {
console.error(e);
}
});
it("should initialize telemetry when 'codeQL.telemetry.enableTelemetry' is enabled and global 'telemetry.telemetryLevel' is 'all'", async () => {
await telemetryListener.initialize();
it("should initialize telemetry", async () => {
expect(telemetryListener._reporter).toBeDefined();
const reporter: any = telemetryListener._reporter;
expect(reporter.extensionId).toBe("my-id");
@@ -118,80 +68,7 @@ describe("telemetry reporting", () => {
expect(reporter.userOptIn).toBe(true); // enabled
});
it("should initialize telemetry when global 'telemetry.telemetryLevel' is 'off'", async () => {
isTelemetryEnabledSpy.mockReturnValue(false);
await setTelemetryLevel("telemetry", "off");
await telemetryListener.initialize();
expect(telemetryListener._reporter).toBeDefined();
const reporter: any = telemetryListener._reporter;
expect(reporter.userOptIn).toBe(false); // disabled
});
it("should not initialize telemetry when extension option disabled", async () => {
await enableTelemetry("codeQL.telemetry", false);
await telemetryListener.initialize();
expect(telemetryListener._reporter).toBeUndefined();
});
it("should not initialize telemetry when both options disabled", async () => {
await enableTelemetry("codeQL.telemetry", false);
isTelemetryEnabledSpy.mockReturnValue(false);
await setTelemetryLevel("telemetry", "off");
await telemetryListener.initialize();
expect(telemetryListener._reporter).toBeUndefined();
});
it("should dispose telemetry object when re-initializing and should not add multiple", async () => {
await telemetryListener.initialize();
expect(telemetryListener._reporter).toBeDefined();
const firstReporter = telemetryListener._reporter;
await telemetryListener.initialize();
expect(telemetryListener._reporter).toBeDefined();
expect(telemetryListener._reporter).not.toBe(firstReporter);
expect(disposeSpy).toHaveBeenCalledTimes(1);
// initializing a third time continues to dispose
await telemetryListener.initialize();
expect(disposeSpy).toHaveBeenCalledTimes(2);
});
it("should reinitialize reporter when extension setting changes", async () => {
await telemetryListener.initialize();
expect(disposeSpy).not.toHaveBeenCalled();
expect(telemetryListener._reporter).toBeDefined();
// this disables the reporter
await enableTelemetry("codeQL.telemetry", false);
expect(telemetryListener._reporter).toBeUndefined();
expect(disposeSpy).toHaveBeenCalledTimes(1);
// creates a new reporter, but does not dispose again
await enableTelemetry("codeQL.telemetry", true);
expect(telemetryListener._reporter).toBeDefined();
expect(disposeSpy).toHaveBeenCalledTimes(1);
});
it("should set userOptIn to false when global setting changes", async () => {
await telemetryListener.initialize();
const reporter: any = telemetryListener._reporter;
expect(reporter.userOptIn).toBe(true); // enabled
isTelemetryEnabledSpy.mockReturnValue(false);
await setTelemetryLevel("telemetry", "off");
expect(reporter.userOptIn).toBe(false); // disabled
});
it("should send an event", async () => {
await telemetryListener.initialize();
telemetryListener.sendCommandUsage("command-id", 1234, undefined);
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
@@ -208,8 +85,6 @@ describe("telemetry reporting", () => {
});
it("should send a command usage event with an error", async () => {
await telemetryListener.initialize();
telemetryListener.sendCommandUsage(
"command-id",
1234,
@@ -230,7 +105,6 @@ describe("telemetry reporting", () => {
});
it("should send a command usage event with a cli version", async () => {
await telemetryListener.initialize();
telemetryListener.cliVersion = new SemVer("1.2.3");
telemetryListener.sendCommandUsage(
@@ -274,39 +148,7 @@ describe("telemetry reporting", () => {
expect(sendTelemetryErrorEventSpy).not.toHaveBeenCalled();
});
it("should avoid sending an event when telemetry is disabled", async () => {
await telemetryListener.initialize();
await enableTelemetry("codeQL.telemetry", false);
telemetryListener.sendCommandUsage("command-id", 1234, undefined);
telemetryListener.sendCommandUsage("command-id", 1234, new Error());
expect(sendTelemetryEventSpy).not.toHaveBeenCalled();
expect(sendTelemetryErrorEventSpy).not.toHaveBeenCalled();
});
it("should send an event when telemetry is re-enabled", async () => {
await telemetryListener.initialize();
await enableTelemetry("codeQL.telemetry", false);
await enableTelemetry("codeQL.telemetry", true);
telemetryListener.sendCommandUsage("command-id", 1234, undefined);
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
"command-usage",
{
name: "command-id",
status: "Success",
isCanary,
cliVersion: "not-set",
},
{ executionTime: 1234 },
);
expect(sendTelemetryErrorEventSpy).not.toHaveBeenCalled();
});
it("should filter undesired properties from telemetry payload", async () => {
await telemetryListener.initialize();
// Reach into the internal appInsights client to grab our telemetry processor.
const telemetryProcessor: Function = (telemetryListener._reporter as any)
.appInsightsClient._telemetryProcessors[0];
@@ -340,118 +182,7 @@ describe("telemetry reporting", () => {
});
});
const resolveArg =
(index: number) =>
(...args: any[]) =>
Promise.resolve(args[index]);
it("should request permission if popup has never been seen before", async () => {
showInformationMessageSpy.mockImplementation(
resolveArg(3 /* "yes" item */),
);
await ctx.globalState.update("telemetry-request-viewed", false);
expect(env.isTelemetryEnabled).toBe(true);
await enableTelemetry("codeQL.telemetry", false);
await telemetryListener.initialize();
// Wait for user's selection to propagate in settings.
await wait(500);
// Dialog opened, user clicks "yes" and telemetry enabled
expect(showInformationMessageSpy).toHaveBeenCalledTimes(1);
expect(ENABLE_TELEMETRY.getValue()).toBe(true);
expect(ctx.globalState.get("telemetry-request-viewed")).toBe(true);
});
it("should prevent telemetry if permission is denied", async () => {
showInformationMessageSpy.mockImplementation(resolveArg(4 /* "no" item */));
await ctx.globalState.update("telemetry-request-viewed", false);
await enableTelemetry("codeQL.telemetry", true);
await telemetryListener.initialize();
// Dialog opened, user clicks "no" and telemetry disabled
expect(showInformationMessageSpy).toHaveBeenCalledTimes(1);
expect(ENABLE_TELEMETRY.getValue()).toBe(false);
expect(ctx.globalState.get("telemetry-request-viewed")).toBe(true);
});
it("should unchange telemetry if permission dialog is dismissed", async () => {
showInformationMessageSpy.mockResolvedValue(undefined /* cancelled */);
await ctx.globalState.update("telemetry-request-viewed", false);
// this causes requestTelemetryPermission to be called
await enableTelemetry("codeQL.telemetry", false);
// Dialog opened, and user closes without interacting with it
expect(showInformationMessageSpy).toHaveBeenCalledTimes(1);
expect(ENABLE_TELEMETRY.getValue()).toBe(false);
// dialog was canceled, so should not have marked as viewed
expect(ctx.globalState.get("telemetry-request-viewed")).toBe(false);
});
it("should unchange telemetry if permission dialog is cancelled if starting as true", async () => {
await enableTelemetry("codeQL.telemetry", false);
// as before, except start with telemetry enabled. It should _stay_ enabled if the
// dialog is canceled.
showInformationMessageSpy.mockResolvedValue(undefined /* cancelled */);
await ctx.globalState.update("telemetry-request-viewed", false);
// this causes requestTelemetryPermission to be called
await enableTelemetry("codeQL.telemetry", true);
// Dialog opened, and user closes without interacting with it
// Telemetry state should not have changed
expect(showInformationMessageSpy).toHaveBeenCalledTimes(1);
expect(ENABLE_TELEMETRY.getValue()).toBe(true);
// dialog was canceled, so should not have marked as viewed
expect(ctx.globalState.get("telemetry-request-viewed")).toBe(false);
});
it("should avoid showing dialog if global telemetry is disabled", async () => {
// when telemetry is disabled globally, we never want to show the
// opt in/out dialog. We just assume that codeql telemetry should
// remain disabled as well.
// If the user ever turns global telemetry back on, then we can
// show the dialog.
isTelemetryEnabledSpy.mockReturnValue(false);
await setTelemetryLevel("telemetry", "off");
await ctx.globalState.update("telemetry-request-viewed", false);
await telemetryListener.initialize();
// popup should not be shown even though we have initialized telemetry
expect(showInformationMessageSpy).not.toHaveBeenCalled();
});
// This test is failing because codeQL.canary is not a registered configuration.
// We do not want to have it registered because we don't want this item
// appearing in the settings page. It needs to only be set by users we tell
// about it to.
// At this point, I see no other way of testing re-requesting permission.
xit("should request permission again when user changes canary setting", async () => {
// initially, both canary and telemetry are false
await workspace.getConfiguration().update("codeQL.canary", false);
await enableTelemetry("codeQL.telemetry", false);
await ctx.globalState.update("telemetry-request-viewed", true);
await telemetryListener.initialize();
showInformationMessageSpy.mockResolvedValue(undefined /* cancelled */);
// set canary to true
await workspace.getConfiguration().update("codeQL.canary", true);
// now, we should have to click through the telemetry requestor again
expect(ctx.globalState.get("telemetry-request-viewed")).toBe(false);
expect(showInformationMessageSpy).toHaveBeenCalledTimes(1);
});
it("should send a ui-interaction telemetry event", async () => {
await telemetryListener.initialize();
telemetryListener.sendUIInteraction("test");
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
@@ -467,8 +198,6 @@ describe("telemetry reporting", () => {
});
it("should send a ui-interaction telemetry event with a cli version", async () => {
await telemetryListener.initialize();
telemetryListener.cliVersion = new SemVer("1.2.3");
telemetryListener.sendUIInteraction("test");
@@ -485,8 +214,6 @@ describe("telemetry reporting", () => {
});
it("should send an error telemetry event", async () => {
await telemetryListener.initialize();
telemetryListener.sendError(redactableError`test`);
expect(sendTelemetryEventSpy).not.toHaveBeenCalled();
@@ -503,7 +230,6 @@ describe("telemetry reporting", () => {
});
it("should send an error telemetry event with a cli version", async () => {
await telemetryListener.initialize();
telemetryListener.cliVersion = new SemVer("1.2.3");
telemetryListener.sendError(redactableError`test`);
@@ -522,8 +248,6 @@ describe("telemetry reporting", () => {
});
it("should redact error message contents", async () => {
await telemetryListener.initialize();
telemetryListener.sendError(
redactableError`test message with secret information: ${42} and more ${"secret"} parts`,
);
@@ -543,8 +267,6 @@ describe("telemetry reporting", () => {
});
it("should send config telemetry event", async () => {
await telemetryListener.initialize();
telemetryListener.sendConfigInformation({
testKey: "testValue",
testKey2: "42",
@@ -563,26 +285,6 @@ describe("telemetry reporting", () => {
expect(sendTelemetryErrorEventSpy).not.toHaveBeenCalled();
});
async function enableTelemetry(section: string, value: boolean | undefined) {
await workspace
.getConfiguration(section)
.update("enableTelemetry", value, ConfigurationTarget.Global);
// Need to wait some time since the onDidChangeConfiguration listeners fire
// asynchronously. Must ensure they to complete in order to have a successful test.
await wait(100);
}
async function setTelemetryLevel(section: string, value: string | undefined) {
await workspace
.getConfiguration(section)
.update("telemetryLevel", value, ConfigurationTarget.Global);
// Need to wait some time since the onDidChangeConfiguration listeners fire
// asynchronously. Must ensure they to complete in order to have a successful test.
await wait(100);
}
async function wait(ms = 0) {
return new Promise((resolve) => setTimeout(resolve, ms));
}