microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
align-android-launch-command

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/services/surveyService/surveyService.ts

241lines · modeblame

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