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" ;
7
3
import 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" ;
16
5
import type { TelemetryClient } from "applicationinsights" ;
17
6
import { extLogger } from "../logging/vscode" ;
18
7
import { UserCancellationException } from "./progress" ;
19
- import { showBinaryChoiceWithUrlDialog } from "./dialog" ;
20
8
import type { RedactableError } from "../errors" ;
21
9
import type { SemVer } from "semver" ;
22
10
import type { AppTelemetry } from "../telemetry" ;
23
11
import type { EnvelopeTelemetry } from "applicationinsights/out/Declarations/Contracts" ;
12
+ import type { Disposable } from "../disposable-object" ;
24
13
25
14
// Key is injected at build time through the APP_INSIGHTS_KEY environment variable.
26
15
const key = "REPLACE-APP-INSIGHTS-KEY" ;
@@ -55,80 +44,25 @@ const baseDataPropertiesToRemove = [
55
44
56
45
const NOT_SET_CLI_VERSION = "not-set" ;
57
46
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 ;
63
49
64
50
private cliVersionStr = NOT_SET_CLI_VERSION ;
65
51
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.
124
55
this . reporter = new TelemetryReporter (
125
- this . id ,
126
- this . version ,
127
- this . key ,
56
+ id ,
57
+ version ,
58
+ key ,
128
59
/* anonymize stack traces */ true ,
129
60
) ;
130
- this . push ( this . reporter ) ;
131
61
62
+ this . addTelemetryProcessor ( ) ;
63
+ }
64
+
65
+ private addTelemetryProcessor ( ) {
132
66
// The appInsightsClient field is private but we want to access it anyway
133
67
const client = this . reporter [ "appInsightsClient" ] as TelemetryClient ;
134
68
if ( client ) {
@@ -151,14 +85,10 @@ export class ExtensionTelemetryListener
151
85
}
152
86
153
87
dispose ( ) {
154
- super . dispose ( ) ;
155
- void this . reporter ?. dispose ( ) ;
88
+ void this . reporter . dispose ( ) ;
156
89
}
157
90
158
91
sendCommandUsage ( name : string , executionTime : number , error ?: Error ) : void {
159
- if ( ! this . reporter ) {
160
- return ;
161
- }
162
92
const status = ! error
163
93
? CommandCompletion . Success
164
94
: error instanceof UserCancellationException
@@ -178,10 +108,6 @@ export class ExtensionTelemetryListener
178
108
}
179
109
180
110
sendUIInteraction ( name : string ) : void {
181
- if ( ! this . reporter ) {
182
- return ;
183
- }
184
-
185
111
this . reporter . sendTelemetryEvent (
186
112
"ui-interaction" ,
187
113
{
@@ -197,10 +123,6 @@ export class ExtensionTelemetryListener
197
123
error : RedactableError ,
198
124
extraProperties ?: { [ key : string ] : string } ,
199
125
) : void {
200
- if ( ! this . reporter ) {
201
- return ;
202
- }
203
-
204
126
const properties : { [ key : string ] : string } = {
205
127
isCanary : isCanary ( ) . toString ( ) ,
206
128
cliVersion : this . cliVersionStr ,
@@ -215,10 +137,6 @@ export class ExtensionTelemetryListener
215
137
}
216
138
217
139
sendConfigInformation ( config : Record < string , string > ) : void {
218
- if ( ! this . reporter ) {
219
- return ;
220
- }
221
-
222
140
this . reporter . sendTelemetryEvent (
223
141
"config" ,
224
142
{
@@ -230,37 +148,6 @@ export class ExtensionTelemetryListener
230
148
) ;
231
149
}
232
150
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
-
264
151
/**
265
152
* Exposed for testing
266
153
*/
@@ -271,21 +158,45 @@ export class ExtensionTelemetryListener
271
158
set cliVersion ( version : SemVer | undefined ) {
272
159
this . cliVersionStr = version ? version . toString ( ) : NOT_SET_CLI_VERSION ;
273
160
}
161
+ }
274
162
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
+ ) ;
279
190
}
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 ) ;
289
200
}
290
201
291
202
/**
@@ -301,15 +212,28 @@ export async function initializeTelemetry(
301
212
if ( telemetryListener !== undefined ) {
302
213
throw new Error ( "Telemetry is already initialized" ) ;
303
214
}
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
+
304
231
telemetryListener = new ExtensionTelemetryListener (
305
232
extension . id ,
306
233
extension . packageJSON . version ,
307
234
key ,
308
- ctx ,
309
235
) ;
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 ( ) ;
313
236
ctx . subscriptions . push ( telemetryListener ) ;
237
+
314
238
return telemetryListener ;
315
239
}
0 commit comments