Skip to content

Commit b062eee

Browse files
committed
More work on telemetry
* Extract commandRunner to its own file * Add an option to log all telemetry commands to the console * Add a popup (text to be determined) that requires users to accept telemetry before using. * Convert telemetry to opt-in instead of opt-out * Add a "canary" setting that users can use to opt-in to unreleased features.
1 parent e97ef3a commit b062eee

24 files changed

Lines changed: 662 additions & 319 deletions

.github/TELEMETRY.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Telemetry in the CodeQL for VS Code
2+
3+
## Why do you collect data?
4+
5+
GitHub collects usage data and metrics to help us improve Salesforce Extensions for VS Code.
6+
7+
## What data is collected
8+
9+
GitHub collects anonymous information related to the usage of the extensions. The data collected are:
10+
11+
- Which commands are run.
12+
- The time taken for each command.
13+
- Anonymized stack trace and error message of any errors that are thrown from inside the extension.
14+
- Anonymous GUID to uniquely identify an installation.
15+
- IP address of the client sending the telemetry data. This IP address is not stored. It is discarded immediately after the telemetry data is received.
16+
17+
## How do I disable telemetry reporting?
18+
19+
You can disable telemetry reporting by setting `codeQL.telemetry.enableTelemetry` to `false` in your settings.
20+
21+
Additionally, telemetry will be disabled if the global `telemetry.enableTelemetry` is set to `false`. For more information see [Microsoft’s documentation](https://code.visualstudio.com/docs/supporting/faq#_how-to-disable-telemetry-reporting).

extensions/ql-vscode/package-lock.json

Lines changed: 47 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extensions/ql-vscode/package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,15 @@
177177
},
178178
"codeQL.telemetry.enableTelemetry": {
179179
"type": "boolean",
180-
"default": true,
180+
"default": false,
181+
"scope": "application",
181182
"markdownDescription": "Specifies whether to enable Code QL telemetry. This setting AND the global `#telemetry.enableTelemetry#` setting must be checked for telemetry to be sent."
183+
},
184+
"codeQL.telemetry.logTelemetry": {
185+
"type": "boolean",
186+
"default": false,
187+
"scope": "application",
188+
"markdownDescription": "Specifies whether or not to write telemetry events to the extension log."
182189
}
183190
}
184191
},
@@ -773,6 +780,7 @@
773780
"@typescript-eslint/eslint-plugin": "~2.23.0",
774781
"@typescript-eslint/parser": "~2.23.0",
775782
"ansi-colors": "^4.1.1",
783+
"applicationinsights": "^1.8.7",
776784
"chai": "^4.2.0",
777785
"chai-as-promised": "~7.1.1",
778786
"css-loader": "~3.1.0",

extensions/ql-vscode/src/astViewer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { DatabaseItem } from './databases';
1818
import { UrlValue, BqrsId } from './pure/bqrs-cli-types';
1919
import { showLocation } from './interface-utils';
2020
import { isStringLoc, isWholeFileLoc, isLineColumnLoc } from './pure/bqrs-utils';
21-
import { commandRunner } from './helpers';
21+
import { commandRunner } from './commandRunner';
2222
import { DisposableObject } from './vscode-utils/disposable-object';
2323

2424
export interface AstItem {
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import {
2+
CancellationToken,
3+
ProgressOptions,
4+
window as Window,
5+
commands,
6+
Disposable,
7+
ProgressLocation
8+
} from 'vscode';
9+
import { showAndLogErrorMessage, showAndLogWarningMessage } from './helpers';
10+
import { logger } from './logging';
11+
import { sendCommandUsage } from './telemetry';
12+
13+
export class UserCancellationException extends Error {
14+
/**
15+
* @param message The error message
16+
* @param silent If silent is true, then this exception will avoid showing a warning message to the user.
17+
*/
18+
constructor(message?: string, public readonly silent = false) {
19+
super(message);
20+
}
21+
}
22+
23+
export interface ProgressUpdate {
24+
/**
25+
* The current step
26+
*/
27+
step: number;
28+
/**
29+
* The maximum step. This *should* be constant for a single job.
30+
*/
31+
maxStep: number;
32+
/**
33+
* The current progress message
34+
*/
35+
message: string;
36+
}
37+
38+
export type ProgressCallback = (p: ProgressUpdate) => void;
39+
40+
/**
41+
* A task that handles command invocations from `commandRunner`
42+
* and includes a progress monitor.
43+
*
44+
*
45+
* Arguments passed to the command handler are passed along,
46+
* untouched to this `ProgressTask` instance.
47+
*
48+
* @param progress a progress handler function. Call this
49+
* function with a `ProgressUpdate` instance in order to
50+
* denote some progress being achieved on this task.
51+
* @param token a cencellation token
52+
* @param args arguments passed to this task passed on from
53+
* `commands.registerCommand`.
54+
*/
55+
export type ProgressTask<R> = (
56+
progress: ProgressCallback,
57+
token: CancellationToken,
58+
...args: any[]
59+
) => Thenable<R>;
60+
61+
/**
62+
* A task that handles command invocations from `commandRunner`.
63+
* Arguments passed to the command handler are passed along,
64+
* untouched to this `NoProgressTask` instance.
65+
*
66+
* @param args arguments passed to this task passed on from
67+
* `commands.registerCommand`.
68+
*/
69+
type NoProgressTask = ((...args: any[]) => Promise<any>);
70+
71+
/**
72+
* This mediates between the kind of progress callbacks we want to
73+
* write (where we *set* current progress position and give
74+
* `maxSteps`) and the kind vscode progress api expects us to write
75+
* (which increment progress by a certain amount out of 100%).
76+
*
77+
* Where possible, the `commandRunner` function below should be used
78+
* instead of this function. The commandRunner is meant for wrapping
79+
* top-level commands and provides error handling and other support
80+
* automatically.
81+
*
82+
* Only use this function if you need a progress monitor and the
83+
* control flow does not always come from a command (eg- during
84+
* extension activation, or from an internal language server
85+
* request).
86+
*/
87+
export function withProgress<R>(
88+
options: ProgressOptions,
89+
task: ProgressTask<R>,
90+
...args: any[]
91+
): Thenable<R> {
92+
let progressAchieved = 0;
93+
return Window.withProgress(options,
94+
(progress, token) => {
95+
return task(p => {
96+
const { message, step, maxStep } = p;
97+
const increment = 100 * (step - progressAchieved) / maxStep;
98+
progressAchieved = step;
99+
progress.report({ message, increment });
100+
}, token, ...args);
101+
});
102+
}
103+
104+
/**
105+
* A generic wrapper for command registration. This wrapper adds uniform error handling for commands.
106+
*
107+
* In this variant of the command runner, no progress monitor is used.
108+
*
109+
* @param commandId The ID of the command to register.
110+
* @param task The task to run. It is passed directly to `commands.registerCommand`. Any
111+
* arguments to the command handler are passed on to the task.
112+
*/
113+
export function commandRunner(
114+
commandId: string,
115+
task: NoProgressTask,
116+
): Disposable {
117+
return commands.registerCommand(commandId, async (...args: any[]) => {
118+
const startTIme = Date.now();
119+
let error: Error | undefined;
120+
121+
try {
122+
return await task(...args);
123+
} catch (e) {
124+
error = e;
125+
if (e instanceof UserCancellationException) {
126+
// User has cancelled this action manually
127+
if (e.silent) {
128+
logger.log(e.message);
129+
} else {
130+
showAndLogWarningMessage(e.message);
131+
}
132+
} else {
133+
showAndLogErrorMessage(e.message || e);
134+
}
135+
return undefined;
136+
} finally {
137+
const executionTime = Date.now() - startTIme;
138+
sendCommandUsage(commandId, executionTime, error);
139+
}
140+
});
141+
}
142+
143+
/**
144+
* A generic wrapper for command registration. This wrapper adds uniform error handling,
145+
* progress monitoring, and cancellation for commands.
146+
*
147+
* @param commandId The ID of the command to register.
148+
* @param task The task to run. It is passed directly to `commands.registerCommand`. Any
149+
* arguments to the command handler are passed on to the task after the progress callback
150+
* and cancellation token.
151+
* @param progressOptions Progress options to be sent to the progress monitor.
152+
*/
153+
export function commandRunnerWithProgress<R>(
154+
commandId: string,
155+
task: ProgressTask<R>,
156+
progressOptions: Partial<ProgressOptions>
157+
): Disposable {
158+
return commands.registerCommand(commandId, async (...args: any[]) => {
159+
const startTIme = Date.now();
160+
let error: Error | undefined;
161+
const progressOptionsWithDefaults = {
162+
location: ProgressLocation.Notification,
163+
...progressOptions
164+
};
165+
try {
166+
return await withProgress(progressOptionsWithDefaults, task, ...args);
167+
} catch (e) {
168+
error = e;
169+
if (e instanceof UserCancellationException) {
170+
// User has cancelled this action manually
171+
if (e.silent) {
172+
logger.log(e.message);
173+
} else {
174+
showAndLogWarningMessage(e.message);
175+
}
176+
} else {
177+
showAndLogErrorMessage(e.message || e);
178+
}
179+
return undefined;
180+
} finally {
181+
const executionTime = Date.now() - startTIme;
182+
sendCommandUsage(commandId, executionTime, error);
183+
}
184+
});
185+
}

extensions/ql-vscode/src/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ const DEBUG_SETTING = new Setting('debug', RUNNING_QUERIES_SETTING);
7171
const RUNNING_TESTS_SETTING = new Setting('runningTests', ROOT_SETTING);
7272
const RESULTS_DISPLAY_SETTING = new Setting('resultsDisplay', ROOT_SETTING);
7373

74+
export const LOG_TELEMETRY = new Setting('telemetry.logTelemetry', ROOT_SETTING);
7475
export const ENABLE_TELEMETRY = new Setting('telemetry.enableTelemetry', ROOT_SETTING);
7576
export const NUMBER_OF_TEST_THREADS_SETTING = new Setting('numberOfThreads', RUNNING_TESTS_SETTING);
7677
export const MAX_QUERIES = new Setting('maxQueries', RUNNING_QUERIES_SETTING);
@@ -241,3 +242,8 @@ export class CliConfigListener extends ConfigListener implements CliConfig {
241242
* want to enable experimental features, they can add them directly in
242243
* their vscode settings json file.
243244
*/
245+
246+
/**
247+
* Enables canary features of this extension. Recommended for all internal users.
248+
*/
249+
export const CANARY_FEATURES = new Setting('canary', ROOT_SETTING);

extensions/ql-vscode/src/contextual/locationFinder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import fileRangeFromURI from './fileRangeFromURI';
88
import * as messages from '../pure/messages';
99
import { QueryServerClient } from '../queryserver-client';
1010
import { QueryWithResults, compileAndRunQueryAgainstDatabase } from '../run-queries';
11-
import { ProgressCallback } from '../helpers';
11+
import { ProgressCallback } from '../commandRunner';
1212
import { KeyType } from './keyType';
1313
import { qlpackOfDatabase, resolveQueries } from './queryResolver';
1414

extensions/ql-vscode/src/contextual/templateProvider.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import * as vscode from 'vscode';
22

33
import { decodeSourceArchiveUri, encodeArchiveBasePath, zipArchiveScheme } from '../archive-filesystem-provider';
44
import { CodeQLCliServer } from '../cli';
5+
import { ProgressCallback, withProgress } from '../commandRunner';
56
import { DatabaseManager } from '../databases';
6-
import { CachedOperation, ProgressCallback, withProgress } from '../helpers';
7+
import { CachedOperation } from '../helpers';
78
import * as messages from '../pure/messages';
89
import { QueryServerClient } from '../queryserver-client';
910
import { compileAndRunQueryAgainstDatabase, QueryWithResults } from '../run-queries';

extensions/ql-vscode/src/databaseFetcher.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import * as path from 'path';
1313
import { DatabaseManager, DatabaseItem } from './databases';
1414
import {
1515
reportStreamProgress,
16-
ProgressCallback,
1716
showAndLogInformationMessage,
1817
} from './helpers';
19-
import { logger } from './logging';
2018
import { tmpDir } from './run-queries';
19+
import { logger } from './logging';
20+
import { ProgressCallback } from './commandRunner';
2121

2222
/**
2323
* Prompts a user to fetch a database from a remote location. Database is assumed to be an archive file.

0 commit comments

Comments
 (0)