Skip to content

Commit 574a9e3

Browse files
author
Kartik Raj
authored
Added a prompt asking users to enroll back in the insiders program (microsoft#7484)
* Added functionality * Added telemetry * Added tests * Some code reviews * Refactored functionality * Added doc * Refactored the tests * Kode reviews * Code reviews
1 parent 613a0d1 commit 574a9e3

File tree

10 files changed

+490
-195
lines changed

10 files changed

+490
-195
lines changed

news/2 Fixes/7473.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added a prompt asking users to enroll back in the insiders program

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
"ExtensionChannels.reloadToUseInsidersMessage": "Please reload Visual Studio Code to use the insiders build of the Python extension.",
140140
"ExtensionChannels.downloadCompletedOutputMessage": "Insiders build download complete.",
141141
"ExtensionChannels.startingDownloadOutputMessage": "Starting download for Insiders build.",
142+
"ExtensionChannels.optIntoProgramAgainMessage": "It looks like you were previously in the Insiders Program of the Python extension. Would you like to opt into the program again?",
142143
"Interpreters.environmentPromptMessage": "We noticed a new virtual environment has been created. Do you want to select it for the workspace folder?",
143144
"DataScience.restartKernelMessage": "Do you want to restart the IPython kernel? All variables will be lost.",
144145
"DataScience.restartKernelMessageYes": "Yes",

src/client/common/insidersBuild/insidersExtensionPrompt.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,30 @@ import { noop } from '../utils/misc';
1414
import { IExtensionChannelService, IInsiderExtensionPrompt } from './types';
1515

1616
export const insidersPromptStateKey = 'INSIDERS_PROMPT_STATE_KEY';
17+
export const optIntoInsidersPromptAgainStateKey = 'OPT_INTO_INSIDERS_PROGRAM_AGAIN_STATE_KEY';
18+
1719
@injectable()
1820
export class InsidersExtensionPrompt implements IInsiderExtensionPrompt {
1921
public readonly hasUserBeenNotified: IPersistentState<boolean>;
22+
public readonly hasUserBeenAskedToOptInAgain: IPersistentState<boolean>;
2023
constructor(
2124
@inject(IApplicationShell) private readonly appShell: IApplicationShell,
2225
@inject(IExtensionChannelService) private readonly insidersDownloadChannelService: IExtensionChannelService,
2326
@inject(ICommandManager) private readonly cmdManager: ICommandManager,
2427
@inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory
2528
) {
2629
this.hasUserBeenNotified = this.persistentStateFactory.createGlobalPersistentState(insidersPromptStateKey, false);
30+
this.hasUserBeenAskedToOptInAgain = this.persistentStateFactory.createGlobalPersistentState(optIntoInsidersPromptAgainStateKey, false);
2731
}
2832

2933
@traceDecorators.error('Error in prompting to install insiders')
30-
public async notifyToInstallInsiders(): Promise<void> {
31-
const prompts = [ExtensionChannels.yesWeekly(), ExtensionChannels.yesDaily(), DataScienceSurveyBanner.bannerLabelNo()];
32-
const telemetrySelections: ['Yes, weekly', 'Yes, daily', 'No, thanks'] = ['Yes, weekly', 'Yes, daily', 'No, thanks'];
33-
const selection = await this.appShell.showInformationMessage(ExtensionChannels.promptMessage(), ...prompts);
34-
sendTelemetryEvent(EventName.INSIDERS_PROMPT, undefined, { selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined });
35-
await this.hasUserBeenNotified.updateValue(true);
36-
if (!selection) {
37-
return;
38-
}
39-
if (selection === ExtensionChannels.yesWeekly()) {
40-
await this.insidersDownloadChannelService.updateChannel('weekly');
41-
} else if (selection === ExtensionChannels.yesDaily()) {
42-
await this.insidersDownloadChannelService.updateChannel('daily');
43-
}
34+
public async promptToInstallInsiders(): Promise<void> {
35+
await this.promptAndUpdate(ExtensionChannels.promptMessage(), this.hasUserBeenNotified, EventName.INSIDERS_PROMPT);
36+
}
37+
38+
@traceDecorators.error('Error in prompting to enroll back to insiders program')
39+
public async promptToEnrollBackToInsiders(): Promise<void> {
40+
await this.promptAndUpdate(ExtensionChannels.optIntoProgramAgainMessage(), this.hasUserBeenAskedToOptInAgain, EventName.OPT_INTO_INSIDERS_AGAIN_PROMPT);
4441
}
4542

4643
@traceDecorators.error('Error in prompting to reload')
@@ -54,4 +51,20 @@ export class InsidersExtensionPrompt implements IInsiderExtensionPrompt {
5451
this.cmdManager.executeCommand('workbench.action.reloadWindow').then(noop);
5552
}
5653
}
54+
55+
private async promptAndUpdate(message: string, hasPromptBeenShownAlreadyState: IPersistentState<boolean>, telemetryEventKey: EventName.INSIDERS_PROMPT | EventName.OPT_INTO_INSIDERS_AGAIN_PROMPT) {
56+
const prompts = [ExtensionChannels.yesWeekly(), ExtensionChannels.yesDaily(), DataScienceSurveyBanner.bannerLabelNo()];
57+
const telemetrySelections: ['Yes, weekly', 'Yes, daily', 'No, thanks'] = ['Yes, weekly', 'Yes, daily', 'No, thanks'];
58+
const selection = await this.appShell.showInformationMessage(message, ...prompts);
59+
sendTelemetryEvent(telemetryEventKey, undefined, { selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined });
60+
await hasPromptBeenShownAlreadyState.updateValue(true);
61+
if (!selection) {
62+
return;
63+
}
64+
if (selection === ExtensionChannels.yesWeekly()) {
65+
await this.insidersDownloadChannelService.updateChannel('weekly');
66+
} else if (selection === ExtensionChannels.yesDaily()) {
67+
await this.insidersDownloadChannelService.updateChannel('daily');
68+
}
69+
}
5770
}

src/client/common/insidersBuild/insidersExtensionService.ts

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ export class InsidersExtensionService implements IExtensionSingleActivationServi
2828
public async activate() {
2929
this.registerCommandsAndHandlers();
3030
const installChannel = this.extensionChannelService.getChannel();
31-
await this.handleEdgeCases(installChannel);
31+
const alreadyHandled = await this.handleEdgeCases(installChannel);
32+
if (alreadyHandled) {
33+
// Simply return if channel is already handled and doesn't need further handling
34+
return;
35+
}
3236
this.handleChannel(installChannel).ignoreErrors();
3337
}
3438

@@ -45,15 +49,17 @@ export class InsidersExtensionService implements IExtensionSingleActivationServi
4549

4650
/**
4751
* Choose what to do in miscellaneous situations
48-
* * 'Notify to install insiders prompt' - Only when using VSC insiders and if they have not been notified before (usually the first session)
49-
* * 'Resolve discrepency' - When install channel is not in sync with what is installed.
52+
* @returns `true` if install channel is handled in these miscellaneous cases, `false` if install channel needs further handling
5053
*/
51-
public async handleEdgeCases(installChannel: ExtensionChannels): Promise<void> {
52-
if (this.appEnvironment.channel === 'insiders' && !this.insidersPrompt.hasUserBeenNotified.value && this.extensionChannelService.isChannelUsingDefaultConfiguration) {
53-
await this.insidersPrompt.notifyToInstallInsiders();
54-
} else if (installChannel !== 'off' && this.appEnvironment.extensionChannel === 'stable') {
55-
// Install channel is set to "weekly" or "daily" but stable version of extension is installed. Switch channel to "off" to use the installed version
56-
await this.extensionChannelService.updateChannel('off');
54+
public async handleEdgeCases(installChannel: ExtensionChannels): Promise<boolean> {
55+
if (await this.promptToEnrollBackToInsidersIfApplicable(installChannel)) {
56+
return true;
57+
} else if (await this.promptToInstallInsidersIfApplicable()) {
58+
return true;
59+
} else if (await this.setInsidersChannelToOffIfApplicable(installChannel)) {
60+
return true;
61+
} else {
62+
return false;
5763
}
5864
}
5965

@@ -63,4 +69,42 @@ export class InsidersExtensionService implements IExtensionSingleActivationServi
6369
this.disposables.push(this.cmdManager.registerCommand(Commands.SwitchToInsidersDaily, () => this.extensionChannelService.updateChannel('daily')));
6470
this.disposables.push(this.cmdManager.registerCommand(Commands.SwitchToInsidersWeekly, () => this.extensionChannelService.updateChannel('weekly')));
6571
}
72+
73+
/**
74+
* If previously in the Insiders Program but not now, request them enroll in the program again
75+
* @returns `true` if prompt is shown, `false` otherwise
76+
*/
77+
private async promptToEnrollBackToInsidersIfApplicable(installChannel: ExtensionChannels): Promise<boolean> {
78+
if (installChannel === 'off' && !this.extensionChannelService.isChannelUsingDefaultConfiguration) {
79+
// If install channel is explicitly set to off, it means that user has used the insiders program before
80+
await this.insidersPrompt.promptToEnrollBackToInsiders();
81+
return true;
82+
}
83+
return false;
84+
}
85+
86+
/**
87+
* Only when using VSC insiders and if they have not been notified before (usually the first session), notify to enroll into the insiders program
88+
* @returns `true` if prompt is shown, `false` otherwise
89+
*/
90+
private async promptToInstallInsidersIfApplicable(): Promise<boolean> {
91+
if (this.appEnvironment.channel === 'insiders' && !this.insidersPrompt.hasUserBeenNotified.value && this.extensionChannelService.isChannelUsingDefaultConfiguration) {
92+
await this.insidersPrompt.promptToInstallInsiders();
93+
return true;
94+
}
95+
return false;
96+
}
97+
98+
/**
99+
* When install channel is not in sync with what is installed, resolve discrepency by setting channel to "off"
100+
* @returns `true` if channel is set to off, `false` otherwise
101+
*/
102+
private async setInsidersChannelToOffIfApplicable(installChannel: ExtensionChannels): Promise<boolean> {
103+
if (installChannel !== 'off' && this.appEnvironment.extensionChannel === 'stable') {
104+
// Install channel is set to "weekly" or "daily" but stable version of extension is installed. Switch channel to "off" to use the installed version
105+
await this.extensionChannelService.updateChannel('off');
106+
return true;
107+
}
108+
return false;
109+
}
66110
}

src/client/common/insidersBuild/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export interface IInsiderExtensionPrompt {
2525
* Gets updated to `true` once user has been prompted to install insiders.
2626
*/
2727
readonly hasUserBeenNotified: IPersistentState<boolean>;
28-
notifyToInstallInsiders(): Promise<void>;
28+
promptToInstallInsiders(): Promise<void>;
29+
promptToEnrollBackToInsiders(): Promise<void>;
2930
promptToReload(): Promise<void>;
3031
}
3132

src/client/common/utils/localize.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export namespace ExtensionChannels {
6767
export const yesWeekly = localize('ExtensionChannels.yesWeekly', 'Yes, weekly');
6868
export const yesDaily = localize('ExtensionChannels.yesDaily', 'Yes, daily');
6969
export const promptMessage = localize('ExtensionChannels.promptMessage', 'We noticed you are using Visual Studio Code Insiders. Would you like to use the Insiders build of the Python extension?');
70+
export const optIntoProgramAgainMessage = localize('ExtensionChannels.optIntoProgramAgainMessage', 'It looks like you were previously in the Insiders Program of the Python extension. Would you like to opt into the program again?');
7071
export const reloadToUseInsidersMessage = localize('ExtensionChannels.reloadToUseInsidersMessage', 'Please reload Visual Studio Code to use the insiders build of the Python extension.');
7172
export const downloadCompletedOutputMessage = localize('ExtensionChannels.downloadCompletedOutputMessage', 'Insiders build download complete.');
7273
export const startingDownloadOutputMessage = localize('ExtensionChannels.startingDownloadOutputMessage', 'Starting download for Insiders build.');

src/client/telemetry/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export enum EventName {
3232
PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT = 'PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT',
3333
INSIDERS_RELOAD_PROMPT = 'INSIDERS_RELOAD_PROMPT',
3434
INSIDERS_PROMPT = 'INSIDERS_PROMPT',
35+
OPT_INTO_INSIDERS_AGAIN_PROMPT = 'OPT_INTO_INSIDERS_AGAIN_PROMPT',
3536
ENVFILE_VARIABLE_SUBSTITUTION = 'ENVFILE_VARIABLE_SUBSTITUTION',
3637
WORKSPACE_SYMBOLS_BUILD = 'WORKSPACE_SYMBOLS.BUILD',
3738
WORKSPACE_SYMBOLS_GO_TO = 'WORKSPACE_SYMBOLS.GO_TO',

src/client/telemetry/index.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -942,11 +942,21 @@ export interface IEventNamePropertyMapping {
942942
*/
943943
[EventName.INSIDERS_PROMPT]: {
944944
/**
945-
* @type {'Yes, weekly'} When user selects to use "weekly" as extension channel in insiders prompt
946-
* @type {'Yes, daily'} When user selects to use "daily" as extension channel in insiders prompt
947-
* @type {'No, thanks'} When user decides to keep using the same extension channel as before
948-
*
949-
* @type {('Yes, weekly' | 'Yes, daily' | 'No, thanks' | undefined)}
945+
* `Yes, weekly` When user selects to use "weekly" as extension channel in insiders prompt
946+
* `Yes, daily` When user selects to use "daily" as extension channel in insiders prompt
947+
* `No, thanks` When user decides to keep using the same extension channel as before
948+
*/
949+
selection: 'Yes, weekly' | 'Yes, daily' | 'No, thanks' | undefined;
950+
};
951+
/**
952+
* Telemetry event sent with details when user clicks a button in the following prompt
953+
* `Prompt message` :- 'It looks like you were previously in the Insiders Program of the Python extension. Would you like to opt into the program again?'
954+
*/
955+
[EventName.OPT_INTO_INSIDERS_AGAIN_PROMPT]: {
956+
/**
957+
* `Yes, weekly` When user selects to use "weekly" as extension channel
958+
* `Yes, daily` When user selects to use "daily" as extension channel
959+
* `No, thanks` When user decides to keep using the same extension channel as before
950960
*/
951961
selection: 'Yes, weekly' | 'Yes, daily' | 'No, thanks' | undefined;
952962
};

0 commit comments

Comments
 (0)