1- import type {
2- Extension ,
3- ExtensionContext ,
4- ConfigurationChangeEvent ,
5- } from "vscode" ;
6- import { ConfigurationTarget , env } from "vscode" ;
1+ import type { Extension , ExtensionContext } from "vscode" ;
2+ import { ConfigurationTarget , env , Uri , window } from "vscode" ;
73import TelemetryReporter from "vscode-extension-telemetry" ;
8- import {
9- ConfigListener ,
10- CANARY_FEATURES ,
11- ENABLE_TELEMETRY ,
12- LOG_TELEMETRY ,
13- isIntegrationTestMode ,
14- isCanary ,
15- } from "../../config" ;
4+ import { ENABLE_TELEMETRY , isCanary , LOG_TELEMETRY } from "../../config" ;
165import type { TelemetryClient } from "applicationinsights" ;
176import { extLogger } from "../logging/vscode" ;
187import { UserCancellationException } from "./progress" ;
19- import { showBinaryChoiceWithUrlDialog } from "./dialog" ;
208import type { RedactableError } from "../errors" ;
219import type { SemVer } from "semver" ;
2210import type { AppTelemetry } from "../telemetry" ;
2311import type { EnvelopeTelemetry } from "applicationinsights/out/Declarations/Contracts" ;
12+ import type { Disposable } from "../disposable-object" ;
2413
2514// Key is injected at build time through the APP_INSIGHTS_KEY environment variable.
2615const key = "REPLACE-APP-INSIGHTS-KEY" ;
@@ -55,80 +44,25 @@ const baseDataPropertiesToRemove = [
5544
5645const NOT_SET_CLI_VERSION = "not-set" ;
5746
58- export class ExtensionTelemetryListener
59- extends ConfigListener
60- implements AppTelemetry
61- {
62- private reporter ?: TelemetryReporter ;
47+ export class ExtensionTelemetryListener implements AppTelemetry , Disposable {
48+ private readonly reporter : TelemetryReporter ;
6349
6450 private cliVersionStr = NOT_SET_CLI_VERSION ;
6551
66- constructor (
67- private readonly id : string ,
68- private readonly version : string ,
69- private readonly key : string ,
70- private readonly ctx : ExtensionContext ,
71- ) {
72- super ( ) ;
73-
74- env . onDidChangeTelemetryEnabled ( async ( ) => {
75- await this . initialize ( ) ;
76- } ) ;
77- }
78-
79- /**
80- * This function handles changes to relevant configuration elements. There are 2 configuration
81- * ids that this function cares about:
82- *
83- * * `codeQL.telemetry.enableTelemetry`: If this one has changed, then we need to re-initialize
84- * the reporter and the reporter may wind up being removed.
85- * * `codeQL.canary`: A change here could possibly re-trigger a dialog popup.
86- *
87- * Note that the global telemetry setting also gate-keeps whether or not to send telemetry events
88- * to Application Insights. However, this gatekeeping happens inside of the vscode-extension-telemetry
89- * package. So, this does not need to be handled here.
90- *
91- * @param e the configuration change event
92- */
93- async handleDidChangeConfiguration (
94- e : ConfigurationChangeEvent ,
95- ) : Promise < void > {
96- if ( e . affectsConfiguration ( ENABLE_TELEMETRY . qualifiedName ) ) {
97- await this . initialize ( ) ;
98- }
99-
100- // Re-request telemetry so that users can see the dialog again.
101- // Re-request if codeQL.canary is being set to `true` and telemetry
102- // is not currently enabled.
103- if (
104- e . affectsConfiguration ( CANARY_FEATURES . qualifiedName ) &&
105- CANARY_FEATURES . getValue ( ) &&
106- ! ENABLE_TELEMETRY . getValue ( )
107- ) {
108- await this . setTelemetryRequested ( false ) ;
109- await this . requestTelemetryPermission ( ) ;
110- }
111- }
112-
113- async initialize ( ) {
114- await this . requestTelemetryPermission ( ) ;
115-
116- this . disposeReporter ( ) ;
117-
118- if ( ENABLE_TELEMETRY . getValue < boolean > ( ) ) {
119- this . createReporter ( ) ;
120- }
121- }
122-
123- private createReporter ( ) {
52+ constructor ( id : string , version : string , key : string ) {
53+ // We can always initialize this and send events using it because the vscode-extension-telemetry will check
54+ // whether the `telemetry.telemetryLevel` setting is enabled.
12455 this . reporter = new TelemetryReporter (
125- this . id ,
126- this . version ,
127- this . key ,
56+ id ,
57+ version ,
58+ key ,
12859 /* anonymize stack traces */ true ,
12960 ) ;
130- this . push ( this . reporter ) ;
13161
62+ this . addTelemetryProcessor ( ) ;
63+ }
64+
65+ private addTelemetryProcessor ( ) {
13266 // The appInsightsClient field is private but we want to access it anyway
13367 const client = this . reporter [ "appInsightsClient" ] as TelemetryClient ;
13468 if ( client ) {
@@ -151,14 +85,10 @@ export class ExtensionTelemetryListener
15185 }
15286
15387 dispose ( ) {
154- super . dispose ( ) ;
155- void this . reporter ?. dispose ( ) ;
88+ void this . reporter . dispose ( ) ;
15689 }
15790
15891 sendCommandUsage ( name : string , executionTime : number , error ?: Error ) : void {
159- if ( ! this . reporter ) {
160- return ;
161- }
16292 const status = ! error
16393 ? CommandCompletion . Success
16494 : error instanceof UserCancellationException
@@ -178,10 +108,6 @@ export class ExtensionTelemetryListener
178108 }
179109
180110 sendUIInteraction ( name : string ) : void {
181- if ( ! this . reporter ) {
182- return ;
183- }
184-
185111 this . reporter . sendTelemetryEvent (
186112 "ui-interaction" ,
187113 {
@@ -197,10 +123,6 @@ export class ExtensionTelemetryListener
197123 error : RedactableError ,
198124 extraProperties ?: { [ key : string ] : string } ,
199125 ) : void {
200- if ( ! this . reporter ) {
201- return ;
202- }
203-
204126 const properties : { [ key : string ] : string } = {
205127 isCanary : isCanary ( ) . toString ( ) ,
206128 cliVersion : this . cliVersionStr ,
@@ -215,10 +137,6 @@ export class ExtensionTelemetryListener
215137 }
216138
217139 sendConfigInformation ( config : Record < string , string > ) : void {
218- if ( ! this . reporter ) {
219- return ;
220- }
221-
222140 this . reporter . sendTelemetryEvent (
223141 "config" ,
224142 {
@@ -230,37 +148,6 @@ export class ExtensionTelemetryListener
230148 ) ;
231149 }
232150
233- /**
234- * Displays a popup asking the user if they want to enable telemetry
235- * for this extension.
236- */
237- async requestTelemetryPermission ( ) {
238- if ( ! this . wasTelemetryRequested ( ) ) {
239- // if global telemetry is disabled, avoid showing the dialog or making any changes
240- let result = undefined ;
241- if (
242- env . isTelemetryEnabled &&
243- // Avoid showing the dialog if we are in integration test mode.
244- ! isIntegrationTestMode ( )
245- ) {
246- // Extension won't start until this completes.
247- result = await showBinaryChoiceWithUrlDialog (
248- "Does the CodeQL Extension by GitHub have your permission to collect usage data and metrics to help us improve CodeQL for VSCode?" ,
249- "https://codeql.github.com/docs/codeql-for-visual-studio-code/about-telemetry-in-codeql-for-visual-studio-code" ,
250- ) ;
251- }
252- if ( result !== undefined ) {
253- await Promise . all ( [
254- this . setTelemetryRequested ( true ) ,
255- ENABLE_TELEMETRY . updateValue < boolean > (
256- result ,
257- ConfigurationTarget . Global ,
258- ) ,
259- ] ) ;
260- }
261- }
262- }
263-
264151 /**
265152 * Exposed for testing
266153 */
@@ -271,21 +158,45 @@ export class ExtensionTelemetryListener
271158 set cliVersion ( version : SemVer | undefined ) {
272159 this . cliVersionStr = version ? version . toString ( ) : NOT_SET_CLI_VERSION ;
273160 }
161+ }
274162
275- private disposeReporter ( ) {
276- if ( this . reporter ) {
277- void this . reporter . dispose ( ) ;
278- this . reporter = undefined ;
163+ async function notifyTelemetryChange ( ) {
164+ const continueItem = { title : "Continue" , isCloseAffordance : false } ;
165+ const vsCodeTelemetryItem = {
166+ title : "More Information about VS Code Telemetry" ,
167+ isCloseAffordance : false ,
168+ } ;
169+ const codeqlTelemetryItem = {
170+ title : "More Information about CodeQL Telemetry" ,
171+ isCloseAffordance : false ,
172+ } ;
173+ let chosenItem ;
174+
175+ do {
176+ chosenItem = await window . showInformationMessage (
177+ "The CodeQL extension now follows VS Code's telemetry settings. VS Code telemetry is currently enabled. Learn how to update your telemetry settings by clicking the links below." ,
178+ { modal : true } ,
179+ continueItem ,
180+ vsCodeTelemetryItem ,
181+ codeqlTelemetryItem ,
182+ ) ;
183+ if ( chosenItem === vsCodeTelemetryItem ) {
184+ await env . openExternal (
185+ Uri . parse (
186+ "https://code.visualstudio.com/docs/getstarted/telemetry" ,
187+ true ,
188+ ) ,
189+ ) ;
279190 }
280- }
281-
282- private wasTelemetryRequested ( ) : boolean {
283- return ! ! this . ctx . globalState . get < boolean > ( " telemetry-request-viewed" ) ;
284- }
285-
286- private async setTelemetryRequested ( newValue : boolean ) : Promise < void > {
287- await this . ctx . globalState . update ( "telemetry-request-viewed" , newValue ) ;
288- }
191+ if ( chosenItem === codeqlTelemetryItem ) {
192+ await env . openExternal (
193+ Uri . parse (
194+ "https://docs.github.com/en/code-security/codeql-for-vs-code/using-the-advanced-functionality-of-the-codeql-for-vs-code-extension/ telemetry-in-codeql-for-visual-studio-code" ,
195+ true ,
196+ ) ,
197+ ) ;
198+ }
199+ } while ( chosenItem !== continueItem ) ;
289200}
290201
291202/**
@@ -301,15 +212,28 @@ export async function initializeTelemetry(
301212 if ( telemetryListener !== undefined ) {
302213 throw new Error ( "Telemetry is already initialized" ) ;
303214 }
215+
216+ if ( ENABLE_TELEMETRY . getValue < boolean | undefined > ( ) === false ) {
217+ if ( env . isTelemetryEnabled ) {
218+ // Await this so that the user is notified before any telemetry is sent
219+ await notifyTelemetryChange ( ) ;
220+ }
221+
222+ // Remove the deprecated telemetry setting
223+ ENABLE_TELEMETRY . updateValue ( undefined , ConfigurationTarget . Global ) ;
224+ ENABLE_TELEMETRY . updateValue ( undefined , ConfigurationTarget . Workspace ) ;
225+ ENABLE_TELEMETRY . updateValue (
226+ undefined ,
227+ ConfigurationTarget . WorkspaceFolder ,
228+ ) ;
229+ }
230+
304231 telemetryListener = new ExtensionTelemetryListener (
305232 extension . id ,
306233 extension . packageJSON . version ,
307234 key ,
308- ctx ,
309235 ) ;
310- // do not await initialization, since doing so will sometimes cause a modal popup.
311- // this is a particular problem during integration tests, which will hang if a modal popup is displayed.
312- void telemetryListener . initialize ( ) ;
313236 ctx . subscriptions . push ( telemetryListener ) ;
237+
314238 return telemetryListener ;
315239}
0 commit comments