microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.8.1

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/extension/services/surveyService/surveyService.ts

242lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as vscode from "vscode";
5import { IConfig, retryDownloadConfig } from "../remoteConfigHelper";
6import { ExtensionConfigManager } from "../../extensionConfigManager";
7import { TelemetryHelper } from "../../../common/telemetryHelper";
8import { Telemetry } from "../../../common/telemetry";
9import { areSameDates, getRandomIntInclusive } from "../../../common/utils";
10import { Delayer } from "../../../common/node/promise";
11import * as nls from "vscode-nls";
12nls.config({
13 messageFormat: nls.MessageFormat.bundle,
14 bundleFormat: nls.BundleFormat.standalone,
15})();
16const localize = nls.loadMessageBundle();
17
18enum SurveyNotificationReaction {
19 ACCEPT = "accept",
20 CANCEL = "cancel",
21}
22
23interface RemoteSurveyConfig extends IConfig {
24 shortPeriodToRemind: number;
25 longPeriodToRemind: number;
26 popCoveragePercent: number;
27 surveyName: string;
28 surveyUrl: string;
29 enabled: boolean;
30}
31
32export interface SurveyConfig extends RemoteSurveyConfig {
33 daysLeftBeforeSurvey: number;
34 lastExtensionUsageDate?: Date;
35}
36
37export class SurveyService implements vscode.Disposable {
38 private static instance: SurveyService;
39
40 private readonly SURVEY_CONFIG_NAME: string = "surveyConfig";
41 private readonly MAX_WAIT_TIME_TO_SHOW_SURVEY_IN_MINUTES: number = 30;
42 private readonly MIN_WAIT_TIME_TO_SHOW_SURVEY_IN_MINUTES: number = 5;
43 private readonly endpointURL: string =
44 "https://microsoft.github.io/vscode-react-native/surveys/surveyConfig.json";
45 private readonly downloadConfigRequest: Promise<RemoteSurveyConfig>;
46
47 private cancellationTokenSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource();
48 private _surveyConfig: SurveyConfig | null = null;
49 private extensionFirstTimeInstalled: boolean = false;
50 private promptDelayer: Delayer<Promise<void>> = new Delayer();
51
52 public static getInstance(): SurveyService {
53 if (!SurveyService.instance) {
54 SurveyService.instance = new SurveyService();
55 }
56
57 return SurveyService.instance;
58 }
59
60 private constructor() {
61 this.downloadConfigRequest = retryDownloadConfig<RemoteSurveyConfig>(
62 this.endpointURL,
63 this.cancellationTokenSource,
64 );
65 }
66
67 public async promptSurvey(): Promise<void> {
68 await this.initializeSurveyConfig();
69
70 if (!this.surveyConfig.enabled) {
71 return;
72 }
73
74 const curDate: Date = new Date();
75
76 if (this.surveyConfig.daysLeftBeforeSurvey === 0) {
77 if (this.isCandidate()) {
78 this.promptDelayer.runWihtDelay(async () => {
79 await this.showSurveyNotification();
80 this.surveyConfig.daysLeftBeforeSurvey = this.surveyConfig.longPeriodToRemind;
81 this.saveSurveyConfig(this.surveyConfig);
82 }, this.calculateSurveyNotificationDelay());
83 } else {
84 this.surveyConfig.daysLeftBeforeSurvey = this.surveyConfig.shortPeriodToRemind;
85 }
86 } else if (
87 this.surveyConfig.lastExtensionUsageDate &&
88 !areSameDates(curDate, this.surveyConfig.lastExtensionUsageDate) &&
89 this.surveyConfig.daysLeftBeforeSurvey > 0
90 ) {
91 this.surveyConfig.daysLeftBeforeSurvey--;
92 }
93
94 this.surveyConfig.lastExtensionUsageDate = curDate;
95 this.saveSurveyConfig(this.surveyConfig);
96 }
97
98 public setExtensionFirstTimeInstalled(extensionFirstTimeInstalled: boolean): void {
99 this.extensionFirstTimeInstalled = extensionFirstTimeInstalled;
100 }
101
102 public dispose(): void {
103 this.cancellationTokenSource.cancel();
104 this.cancellationTokenSource.dispose();
105 this.promptDelayer.dispose();
106 }
107
108 private async initializeSurveyConfig(): Promise<void> {
109 if (this._surveyConfig) {
110 return;
111 }
112
113 let surveyConfig: SurveyConfig;
114 if (!ExtensionConfigManager.config.has(this.SURVEY_CONFIG_NAME)) {
115 surveyConfig = {
116 shortPeriodToRemind: 30,
117 longPeriodToRemind: 90,
118 popCoveragePercent: 0.1,
119 enabled: false,
120 daysLeftBeforeSurvey: this.extensionFirstTimeInstalled ? 30 : 3,
121 surveyName: "none",
122 surveyUrl: "",
123 };
124 } else {
125 surveyConfig = this.prepareRawConfig(
126 ExtensionConfigManager.config.get(this.SURVEY_CONFIG_NAME),
127 );
128 }
129
130 surveyConfig = await this.mergeRemoteConfigToLocal(surveyConfig);
131
132 this.saveSurveyConfig(surveyConfig);
133
134 this._surveyConfig = surveyConfig;
135 }
136
137 private saveSurveyConfig(surveyConfig: SurveyConfig): void {
138 ExtensionConfigManager.config.set(this.SURVEY_CONFIG_NAME, surveyConfig);
139 }
140
141 private calculateSurveyNotificationDelay(): number {
142 return (
143 getRandomIntInclusive(
144 this.MIN_WAIT_TIME_TO_SHOW_SURVEY_IN_MINUTES,
145 this.MAX_WAIT_TIME_TO_SHOW_SURVEY_IN_MINUTES,
146 ) *
147 60 *
148 1000
149 );
150 }
151
152 private async showSurveyNotification(): Promise<void> {
153 const giveFeedbackButtonText = localize("giveFeedback", "Give Feedback");
154 const remindLaterButtonText = localize("remindLater", "Remind Me later");
155 const notificationText = localize(
156 "surveyNotificationText",
157 "Got a moment to help the React Native Tools team? Please tell us about your experience with the extension so far.",
158 );
159
160 this.sendPromptSurveyTelemetry(this.surveyConfig.surveyName);
161
162 const selection = await vscode.window.showInformationMessage(
163 notificationText,
164 giveFeedbackButtonText,
165 remindLaterButtonText,
166 );
167
168 if (!selection || selection === remindLaterButtonText) {
169 this.sendSurveyNotificationReactionTelemetry(
170 this.surveyConfig.surveyName,
171 SurveyNotificationReaction.CANCEL,
172 );
173 }
174 if (selection === giveFeedbackButtonText && this.surveyConfig.surveyUrl) {
175 vscode.env.openExternal(vscode.Uri.parse(this.surveyConfig.surveyUrl));
176 this.sendSurveyNotificationReactionTelemetry(
177 this.surveyConfig.surveyName,
178 SurveyNotificationReaction.ACCEPT,
179 );
180 }
181 }
182
183 private get surveyConfig(): SurveyConfig {
184 if (!this._surveyConfig) {
185 if (!ExtensionConfigManager.config.has(this.SURVEY_CONFIG_NAME)) {
186 throw new Error("Could not find Survey config in the config store.");
187 } else {
188 this._surveyConfig = this.prepareRawConfig(
189 ExtensionConfigManager.config.get(this.SURVEY_CONFIG_NAME),
190 );
191 }
192 }
193 return this._surveyConfig as SurveyConfig;
194 }
195
196 private isCandidate(): boolean {
197 return this.surveyConfig.popCoveragePercent > Math.random();
198 }
199
200 private prepareRawConfig(rawSurveyConfig: SurveyConfig): SurveyConfig {
201 if (rawSurveyConfig.lastExtensionUsageDate) {
202 rawSurveyConfig.lastExtensionUsageDate = new Date(
203 rawSurveyConfig.lastExtensionUsageDate,
204 );
205 }
206 return rawSurveyConfig;
207 }
208
209 private async mergeRemoteConfigToLocal(surveyConfig: SurveyConfig): Promise<SurveyConfig> {
210 const remoteConfig = await this.downloadConfigRequest;
211 surveyConfig.shortPeriodToRemind = remoteConfig.shortPeriodToRemind;
212 surveyConfig.longPeriodToRemind = remoteConfig.longPeriodToRemind;
213 surveyConfig.popCoveragePercent = remoteConfig.popCoveragePercent;
214 surveyConfig.surveyUrl = remoteConfig.surveyUrl;
215 surveyConfig.surveyName = remoteConfig.surveyName;
216 surveyConfig.enabled = remoteConfig.enabled;
217 return surveyConfig;
218 }
219
220 private sendPromptSurveyTelemetry(surveyName: string): void {
221 const promptUserSurveyEvent = TelemetryHelper.createTelemetryEvent("promptUserSurvey", {
222 surveyName,
223 });
224
225 Telemetry.send(promptUserSurveyEvent);
226 }
227
228 private sendSurveyNotificationReactionTelemetry(
229 surveyName: string,
230 surveyNotificationReaction: SurveyNotificationReaction,
231 ): void {
232 const surveyNotificationReactionEvent = TelemetryHelper.createTelemetryEvent(
233 "surveyNotificationReaction",
234 {
235 surveyName,
236 userReaction: surveyNotificationReaction,
237 },
238 );
239
240 Telemetry.send(surveyNotificationReactionEvent);
241 }
242}
243