Skip to content

Commit 497e492

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 fd0f0df commit 497e492

25 files changed

+669
-319
lines changed

.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
@@ -172,8 +172,15 @@
172172
},
173173
"codeQL.telemetry.enableTelemetry": {
174174
"type": "boolean",
175-
"default": true,
175+
"default": false,
176+
"scope": "application",
176177
"markdownDescription": "Specifies whether to enable Code QL telemetry. This setting AND the global `#telemetry.enableTelemetry#` setting must be checked for telemetry to be sent."
178+
},
179+
"codeQL.telemetry.logTelemetry": {
180+
"type": "boolean",
181+
"default": false,
182+
"scope": "application",
183+
"markdownDescription": "Specifies whether or not to write telemetry events to the extension log."
177184
}
178185
}
179186
},
@@ -751,6 +758,7 @@
751758
"@typescript-eslint/eslint-plugin": "~2.23.0",
752759
"@typescript-eslint/parser": "~2.23.0",
753760
"ansi-colors": "^4.1.1",
761+
"applicationinsights": "^1.8.7",
754762
"chai": "^4.2.0",
755763
"chai-as-promised": "~7.1.1",
756764
"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
@@ -17,7 +17,7 @@ import { DatabaseItem } from './databases';
1717
import { UrlValue, BqrsId } from './bqrs-cli-types';
1818
import { showLocation } from './interface-utils';
1919
import { isStringLoc, isWholeFileLoc, isLineColumnLoc } from './bqrs-utils';
20-
import { commandRunner } from './helpers';
20+
import { commandRunner } from './commandRunner';
2121
import { DisposableObject } from './vscode-utils/disposable-object';
2222

2323
export interface AstItem {
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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+
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+
} finally {
136+
const executionTime = Date.now() - startTIme;
137+
sendCommandUsage(commandId, executionTime, error);
138+
}
139+
});
140+
}
141+
142+
/**
143+
* A generic wrapper for command registration. This wrapper adds uniform error handling,
144+
* progress monitoring, and cancellation for commands.
145+
*
146+
* @param commandId The ID of the command to register.
147+
* @param task The task to run. It is passed directly to `commands.registerCommand`. Any
148+
* arguments to the command handler are passed on to the task after the progress callback
149+
* and cancellation token.
150+
* @param progressOptions Progress options to be sent to the progress monitor.
151+
*/
152+
export function commandRunnerWithProgress<R>(
153+
commandId: string,
154+
task: ProgressTask<R>,
155+
progressOptions: Partial<ProgressOptions>
156+
): Disposable {
157+
return commands.registerCommand(commandId, async (...args: any[]) => {
158+
const startTIme = Date.now();
159+
let error: Error | undefined;
160+
const progressOptionsWithDefaults = {
161+
location: ProgressLocation.Notification,
162+
...progressOptions
163+
};
164+
try {
165+
await withProgress(progressOptionsWithDefaults, task, ...args);
166+
} catch (e) {
167+
error = e;
168+
if (e instanceof UserCancellationException) {
169+
// User has cancelled this action manually
170+
if (e.silent) {
171+
logger.log(e.message);
172+
} else {
173+
showAndLogWarningMessage(e.message);
174+
}
175+
} else {
176+
showAndLogErrorMessage(e.message || e);
177+
}
178+
} finally {
179+
const executionTime = Date.now() - startTIme;
180+
sendCommandUsage(commandId, executionTime, error);
181+
}
182+
});
183+
}

extensions/ql-vscode/src/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const MEMORY_SETTING = new Setting('memory', RUNNING_QUERIES_SETTING);
6969
const DEBUG_SETTING = new Setting('debug', RUNNING_QUERIES_SETTING);
7070
const RUNNING_TESTS_SETTING = new Setting('runningTests', ROOT_SETTING);
7171

72+
export const LOG_TELEMETRY = new Setting('telemetry.logTelemetry', ROOT_SETTING);
7273
export const ENABLE_TELEMETRY = new Setting('telemetry.enableTelemetry', ROOT_SETTING);
7374
export const NUMBER_OF_TEST_THREADS_SETTING = new Setting('numberOfThreads', RUNNING_TESTS_SETTING);
7475
export const MAX_QUERIES = new Setting('maxQueries', RUNNING_QUERIES_SETTING);
@@ -234,3 +235,8 @@ export class CliConfigListener extends ConfigListener implements CliConfig {
234235
* want to enable experimental features, they can add them directly in
235236
* their vscode settings json file.
236237
*/
238+
239+
/**
240+
* Enables canary features of this extension. Recommended for all internal users.
241+
*/
242+
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 '../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 '../messages';
89
import { QueryServerClient } from '../queryserver-client';
910
import { compileAndRunQueryAgainstDatabase, QueryWithResults } from '../run-queries';

extensions/ql-vscode/src/databaseFetcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import * as path from 'path';
1212

1313
import { DatabaseManager, DatabaseItem } from './databases';
1414
import {
15-
ProgressCallback,
1615
showAndLogInformationMessage,
1716
} from './helpers';
1817
import { logger } from './logging';
18+
import { ProgressCallback } from './commandRunner';
1919

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

extensions/ql-vscode/src/databases-ui.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ import {
2222
import {
2323
commandRunner,
2424
commandRunnerWithProgress,
25-
getOnDiskWorkspaceFolders,
2625
ProgressCallback,
26+
} from './commandRunner';
27+
import {
28+
getOnDiskWorkspaceFolders,
2729
showAndLogErrorMessage
2830
} from './helpers';
2931
import { logger } from './logging';

0 commit comments

Comments
 (0)