Skip to content

Commit d30e061

Browse files
committed
updated telemetry prefs and reporter
1 parent 6d1c399 commit d30e061

12 files changed

+246
-60
lines changed

vscode/esbuild.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ const createTelemetryConfig = () => {
5757
baseUrl: null,
5858
baseEndpoint: "/vscode/java/sendTelemetry",
5959
version: "/v1"
60+
},
61+
metadata: {
62+
consentSchemaVersion: "v1"
6063
}
6164
}
6265

@@ -73,6 +76,9 @@ const createTelemetryConfig = () => {
7376
baseUrl: process.env.TELEMETRY_API_BASE_URL,
7477
baseEndpoint: process.env.TELEMETRY_API_ENDPOINT,
7578
version: process.env.TELEMETRY_API_VERSION
79+
},
80+
metadata: {
81+
consentSchemaVersion: process.env.CONSENT_SCHEMA_VERSION
7682
}
7783
});
7884

vscode/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,10 @@
245245
"jdk.telemetry.enabled": {
246246
"type": "boolean",
247247
"description": "%jdk.configuration.telemetry.enabled.description%",
248-
"default": false
248+
"default": false,
249+
"tags": [
250+
"telemetry"
251+
]
249252
}
250253
}
251254
},

vscode/src/telemetry/config.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
16-
import { RetryConfig, TelemetryApi } from "./types";
16+
import { RetryConfig, TelemetryApi, TelemetryConfigMetadata } from "./types";
1717
import * as path from 'path';
1818
import * as fs from 'fs';
1919
import { LOGGER } from "../logger";
@@ -24,6 +24,7 @@ export class TelemetryConfiguration {
2424
private static instance: TelemetryConfiguration;
2525
private retryConfig!: RetryConfig;
2626
private apiConfig!: TelemetryApi;
27+
private metadata!: TelemetryConfigMetadata;
2728

2829
public constructor() {
2930
this.initialize();
@@ -54,6 +55,11 @@ export class TelemetryConfiguration {
5455
baseEndpoint: config.telemetryApi.baseEndpoint,
5556
version: config.telemetryApi.version
5657
});
58+
59+
this.metadata = Object.freeze({
60+
consentSchemaVersion: config.metadata.consentSchemaVersion
61+
});
62+
5763
} catch (error: any) {
5864
LOGGER.error("Error occurred while setting up telemetry config");
5965
LOGGER.error(error.message);
@@ -68,4 +74,7 @@ export class TelemetryConfiguration {
6874
return this.apiConfig;
6975
}
7076

77+
public getTelemetryConfigMetadata(): Readonly<TelemetryConfigMetadata> {
78+
return this.metadata;
79+
}
7180
}

vscode/src/telemetry/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const TELEMETRY_CONSENT_VERSION_SCHEMA_KEY = "telemetryConsentSchemaVersion";
2+
export const TELEMETRY_CONSENT_POPUP_TIME_KEY = "telemetryConsentPopupTime";
3+
export const TELEMETRY_SETTING_VALUE_KEY = "telemetrySettingValue";

vscode/src/telemetry/impl/cacheServiceImpl.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class CacheServiceImpl implements CacheService {
3232
try {
3333
const vscGlobalState = globalState.getExtensionContextInfo().getVscGlobalState();
3434
vscGlobalState.update(key, value);
35+
LOGGER.debug(`Updating key: ${key} to ${value}`);
3536
return true;
3637
} catch (err) {
3738
LOGGER.error(`Error while storing ${key} in cache: ${(err as Error).message}`);

vscode/src/telemetry/impl/telemetryEventQueue.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
import { BaseEvent } from "../events/baseEvent";
1717

18-
export class TelemetryEventQueue {
18+
export class TelemetryEventQueue {
1919
private events: BaseEvent<any>[] = [];
2020

2121
public enqueue = (e: BaseEvent<any>): void => {
@@ -35,4 +35,19 @@ export class TelemetryEventQueue {
3535
this.events = [];
3636
return queue;
3737
}
38+
39+
public decreaseSizeOnMaxOverflow = () => {
40+
const seen = new Set<string>();
41+
const newQueueStart = Math.floor(this.size() / 2);
42+
43+
const secondHalf = this.events.slice(newQueueStart);
44+
45+
const uniqueEvents = secondHalf.filter(event => {
46+
if (seen.has(event.NAME)) return false;
47+
seen.add(event.NAME);
48+
return true;
49+
});
50+
51+
this.events = [...uniqueEvents, ...secondHalf];
52+
}
3853
}

vscode/src/telemetry/impl/telemetryPrefs.ts

Lines changed: 143 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,52 +13,166 @@
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
16-
import { ConfigurationChangeEvent, env, workspace } from "vscode";
16+
import { ConfigurationChangeEvent, env, workspace, Disposable } from "vscode";
1717
import { getConfigurationValue, inspectConfiguration, updateConfigurationValue } from "../../configurations/handlers";
1818
import { configKeys } from "../../configurations/configuration";
1919
import { appendPrefixToCommand } from "../../utils";
20+
import { ExtensionContextInfo } from "../../extensionContextInfo";
21+
import { TelemetryPreference } from "../types";
22+
import { cacheService } from "./cacheServiceImpl";
23+
import { TELEMETRY_CONSENT_POPUP_TIME_KEY, TELEMETRY_CONSENT_VERSION_SCHEMA_KEY, TELEMETRY_SETTING_VALUE_KEY } from "../constants";
24+
import { TelemetryConfiguration } from "../config";
25+
import { LOGGER } from "../../logger";
2026

21-
export class TelemetryPrefs {
22-
public isExtTelemetryEnabled: boolean;
27+
export class TelemetrySettings {
28+
private isTelemetryEnabled: boolean;
29+
private extensionPrefs: ExtensionTelemetryPreference;
30+
private vscodePrefs: VscodeTelemetryPreference;
31+
32+
constructor(
33+
extensionContext: ExtensionContextInfo,
34+
private onTelemetryEnableCallback: () => void,
35+
private onTelemetryDisableCallback: () => void,
36+
private triggerPopup: () => void) {
37+
38+
this.extensionPrefs = new ExtensionTelemetryPreference();
39+
this.vscodePrefs = new VscodeTelemetryPreference();
40+
41+
extensionContext.pushSubscription(
42+
this.extensionPrefs.onChangeTelemetrySetting(this.onChangeTelemetrySettingCallback)
43+
);
44+
extensionContext.pushSubscription(
45+
this.vscodePrefs.onChangeTelemetrySetting(this.onChangeTelemetrySettingCallback)
46+
);
47+
48+
this.isTelemetryEnabled = this.checkTelemetryStatus();
49+
this.updateGlobalState();
50+
this.checkConsentVersion();
51+
}
52+
53+
private checkTelemetryStatus = (): boolean => this.extensionPrefs.getIsTelemetryEnabled() && this.vscodePrefs.getIsTelemetryEnabled();
54+
55+
private onChangeTelemetrySettingCallback = () => {
56+
const newTelemetryStatus = this.checkTelemetryStatus();
57+
if (newTelemetryStatus !== this.isTelemetryEnabled) {
58+
this.isTelemetryEnabled = newTelemetryStatus;
59+
cacheService.put(TELEMETRY_SETTING_VALUE_KEY, newTelemetryStatus.toString());
60+
61+
if (newTelemetryStatus) {
62+
this.onTelemetryEnableCallback();
63+
} else {
64+
this.onTelemetryDisableCallback();
65+
}
66+
} else if (this.vscodePrefs.getIsTelemetryEnabled() && !this.extensionPrefs.isTelemetrySettingSet()) {
67+
this.triggerPopup();
68+
}
69+
}
70+
71+
public getIsTelemetryEnabled = (): boolean => this.isTelemetryEnabled;
72+
73+
public isConsentPopupToBeTriggered = (): boolean => {
74+
const isExtensionSettingSet = this.extensionPrefs.isTelemetrySettingSet();
75+
const isVscodeSettingEnabled = this.vscodePrefs.getIsTelemetryEnabled();
76+
77+
const showPopup = !isExtensionSettingSet && isVscodeSettingEnabled;
78+
79+
if (showPopup) {
80+
cacheService.put(TELEMETRY_CONSENT_POPUP_TIME_KEY, Date.now().toString());
81+
}
82+
83+
return showPopup;
84+
}
85+
86+
public updateTelemetrySetting = (value: boolean | undefined): void => {
87+
this.extensionPrefs.updateTelemetryConfig(value);
88+
}
89+
90+
private updateGlobalState(): void {
91+
const cachedValue = cacheService.get(TELEMETRY_SETTING_VALUE_KEY);
92+
93+
if (this.isTelemetryEnabled.toString() !== cachedValue) {
94+
cacheService.put(TELEMETRY_SETTING_VALUE_KEY, this.isTelemetryEnabled.toString());
95+
}
96+
}
97+
98+
private checkConsentVersion(): void {
99+
const cachedVersion = cacheService.get(TELEMETRY_CONSENT_VERSION_SCHEMA_KEY);
100+
const currentVersion = TelemetryConfiguration.getInstance().getTelemetryConfigMetadata()?.consentSchemaVersion;
101+
102+
if (cachedVersion !== currentVersion) {
103+
cacheService.put(TELEMETRY_CONSENT_VERSION_SCHEMA_KEY, currentVersion);
104+
LOGGER.debug("Removing telemetry config from user settings");
105+
if (this.extensionPrefs.isTelemetrySettingSet()) {
106+
this.updateTelemetrySetting(undefined);
107+
}
108+
this.isTelemetryEnabled = false;
109+
}
110+
}
111+
}
112+
113+
class ExtensionTelemetryPreference implements TelemetryPreference {
114+
private isTelemetryEnabled: boolean | undefined;
115+
private readonly CONFIG = appendPrefixToCommand(configKeys.telemetryEnabled);
23116

24117
constructor() {
25-
this.isExtTelemetryEnabled = this.checkTelemetryStatus();
118+
this.isTelemetryEnabled = getConfigurationValue(configKeys.telemetryEnabled, false);
26119
}
27120

28-
private checkTelemetryStatus = (): boolean => {
29-
return getConfigurationValue(configKeys.telemetryEnabled, false);
121+
public getIsTelemetryEnabled = (): boolean => this.isTelemetryEnabled === undefined ? false : this.isTelemetryEnabled;
122+
123+
public onChangeTelemetrySetting = (callback: () => void): Disposable => workspace.onDidChangeConfiguration((e: ConfigurationChangeEvent) => {
124+
if (e.affectsConfiguration(this.CONFIG)) {
125+
this.isTelemetryEnabled = getConfigurationValue(configKeys.telemetryEnabled, false);
126+
callback();
127+
}
128+
});
129+
130+
public updateTelemetryConfig = (value: boolean | undefined): void => {
131+
this.isTelemetryEnabled = value;
132+
updateConfigurationValue(configKeys.telemetryEnabled, value, true);
30133
}
31134

32-
private configPref = (configCommand: string): boolean => {
33-
const config = inspectConfiguration(configCommand);
135+
public isTelemetrySettingSet = (): boolean => {
136+
if (this.isTelemetryEnabled === undefined) return false;
137+
const config = inspectConfiguration(this.CONFIG);
34138
return (
35-
config?.workspaceFolderValue !== undefined ||
36-
config?.workspaceFolderLanguageValue !== undefined ||
37-
config?.workspaceValue !== undefined ||
38-
config?.workspaceLanguageValue !== undefined ||
39139
config?.globalValue !== undefined ||
40140
config?.globalLanguageValue !== undefined
41141
);
42142
}
143+
}
43144

44-
public isExtTelemetryConfigured = (): boolean => {
45-
return this.configPref(appendPrefixToCommand(configKeys.telemetryEnabled));
46-
}
145+
class VscodeTelemetryPreference implements TelemetryPreference {
146+
private isTelemetryEnabled: boolean;
47147

48-
public updateTelemetryEnabledConfig = (value: boolean): void => {
49-
this.isExtTelemetryEnabled = value;
50-
updateConfigurationValue(configKeys.telemetryEnabled, value, true);
148+
constructor() {
149+
this.isTelemetryEnabled = env.isTelemetryEnabled;
51150
}
52151

53-
public didUserDisableVscodeTelemetry = (): boolean => {
54-
return !env.isTelemetryEnabled;
55-
}
152+
public getIsTelemetryEnabled = (): boolean => this.isTelemetryEnabled;
56153

57-
public onDidChangeTelemetryEnabled = () => workspace.onDidChangeConfiguration(
58-
(e: ConfigurationChangeEvent) => {
59-
if (e.affectsConfiguration(appendPrefixToCommand(configKeys.telemetryEnabled))) {
60-
this.isExtTelemetryEnabled = this.checkTelemetryStatus();
61-
}
62-
}
63-
);
64-
}
154+
public onChangeTelemetrySetting = (callback: () => void): Disposable => env.onDidChangeTelemetryEnabled((newSetting: boolean) => {
155+
this.isTelemetryEnabled = newSetting;
156+
callback();
157+
});
158+
}
159+
160+
// Question:
161+
// When consent version is changed, we have to show popup to all the users or only those who had accepted earlier?
162+
163+
// Test cases:
164+
// 1. User accepts consent and VSCode telemetry is set to 'all'. Output: enabled telemetry
165+
// 2. User accepts consent and VSCode telemetry is not set to 'all'. Output: disabled telemetry
166+
// 3. User rejects consent and VSCode telemetry is set to 'all'. Output: disabled telemetry
167+
// 4. User rejects consent and VSCode telemetry is not set to 'all'. Output: disabled telemetry
168+
// 5. User changes from accept to reject consent and VSCode telemetry is set to 'all'. Output: disabled telemetry
169+
// 6. User changes from accept to reject consent and VSCode telemetry is not set to 'all'. Output: disabled telemetry
170+
// 7. User changes from reject to accept consent and VSCode telemetry is set to 'all'. Output: enabled telemetry
171+
// 8. User changes from reject to accept consent and VSCode telemetry is not set to 'all'. Output: disabled telemetry
172+
// 9. User accepts consent and VSCode telemetry is changed from 'all' to 'error'. Output: disabled telemetry
173+
// 10. User accepts consent and VSCode telemetry is changed from 'error' to 'all'. Output: enabled telemetry
174+
// 11. When consent schema version updated, pop up should trigger again.
175+
// 12. When consent schema version updated, pop up should trigger again, if closed without selecting any value and again reloading the screen, it should pop-up again.: Disabled telemetry in settings
176+
// 13. When consent schema version updated, pop up should trigger again, if selected yes and again reloading the screen, it shouldn't pop-up again. Output: Enabled telemetry in settings
177+
// 14. When consent schema version updated, pop up should trigger again, if selected no and again reloading the screen, it shouldn't pop-up again. Output: Disabled telemetry in settings
178+
// 15. When VSCode setting is changed from reject to accept, our pop-up should come.

vscode/src/telemetry/impl/telemetryReporterImpl.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ export class TelemetryReporterImpl implements TelemetryReporter {
4646
}
4747

4848
public closeEvent = (): void => {
49-
5049
const extensionCloseEvent = ExtensionCloseEvent.builder(this.activationTime);
5150
this.addEventToQueue(extensionCloseEvent);
5251

@@ -59,6 +58,10 @@ export class TelemetryReporterImpl implements TelemetryReporter {
5958
this.queue.enqueue(event);
6059
if (this.retryManager.isQueueOverflow(this.queue.size())) {
6160
LOGGER.debug(`Send triggered to queue size overflow`);
61+
if(this.retryManager.IsMaxRetryReached()){
62+
LOGGER.debug('Decreasing size of the queue');
63+
this.queue.decreaseSizeOnMaxOverflow();
64+
}
6265
this.sendEvents();
6366
}
6467
}
@@ -81,9 +84,9 @@ export class TelemetryReporterImpl implements TelemetryReporter {
8184

8285
LOGGER.debug(`Number of events successfully sent: ${response.success.length}`);
8386
LOGGER.debug(`Number of events failed to send: ${response.failures.length}`);
84-
this.handlePostTelemetryResponse(response);
87+
const isResetRetryParams = this.handlePostTelemetryResponse(response);
8588

86-
this.retryManager.startTimer();
89+
this.retryManager.startTimer(isResetRetryParams);
8790
} catch (err: any) {
8891
this.disableReporter = true;
8992
LOGGER.debug(`Error while sending telemetry: ${isError(err) ? err.message : err}`);
@@ -98,11 +101,13 @@ export class TelemetryReporterImpl implements TelemetryReporter {
98101
return [...removedJdkFeatureEvents, ...concatedEvents];
99102
}
100103

101-
private handlePostTelemetryResponse = (response: TelemetryPostResponse) => {
104+
private handlePostTelemetryResponse = (response: TelemetryPostResponse): boolean => {
102105
const eventsToBeEnqueued = this.retryManager.eventsToBeEnqueuedAgain(response);
103106

104107
this.queue.concatQueue(eventsToBeEnqueued);
105108

106109
LOGGER.debug(`Number of failed events enqueuing again: ${eventsToBeEnqueued.length}`);
110+
111+
return eventsToBeEnqueued.length === 0;
107112
}
108113
}

0 commit comments

Comments
 (0)