-
Notifications
You must be signed in to change notification settings - Fork 796
/
Copy pathgoDeveloperSurvey.ts
195 lines (173 loc) · 6.18 KB
/
goDeveloperSurvey.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
/* eslint-disable @typescript-eslint/no-explicit-any */
/*---------------------------------------------------------
* Copyright 2021 The Go Authors. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------*/
'use strict';
import vscode = require('vscode');
import { getGoConfig } from './config';
import { daysBetween, flushSurveyConfig, getStateConfig, minutesBetween, timeMinute } from './goSurvey';
import { GoExtensionContext } from './context';
// Start and end dates of the survey.
export const startDate = new Date('Sep 9 2024 00:00:00 GMT');
export const endDate = new Date('Sep 23 2024 00:00:00 GMT');
// DeveloperSurveyConfig is the set of global properties used to determine if
// we should prompt a user to take the gopls survey.
export interface DeveloperSurveyConfig {
// prompt is true if the user can be prompted to take the survey.
// It is false if the user has responded "Never" to the prompt.
prompt?: boolean;
// datePromptComputed is the date on which the value of the prompt field
// was set. It is usually the same as lastDatePrompted, but not
// necessarily.
datePromptComputed?: Date;
// lastDatePrompted is the most recent date that the user has been prompted.
lastDatePrompted?: Date;
// lastDateAccepted is the most recent date that the user responded "Yes"
// to the survey prompt. The user need not have completed the survey.
lastDateAccepted?: Date;
}
export function maybePromptForDeveloperSurvey(goCtx: GoExtensionContext) {
// First, check the value of the 'go.survey.prompt' setting to see
// if the user has opted out of all survey prompts.
const goConfig = getGoConfig();
if (goConfig.get('survey.prompt') === false) {
return;
}
const now = new Date();
let cfg = shouldPromptForSurvey(now, getDeveloperSurveyConfig());
if (!cfg) {
return;
}
if (!cfg.prompt) {
return;
}
const callback = async () => {
const currentTime = new Date();
const { lastUserAction = new Date() } = goCtx;
// Make sure the user has been idle for at least a minute.
if (minutesBetween(lastUserAction, currentTime) < 1) {
setTimeout(callback, 5 * timeMinute);
return;
}
cfg = await promptForDeveloperSurvey(cfg ?? {}, now);
if (cfg) {
flushSurveyConfig(developerSurveyConfig, cfg);
}
};
callback();
}
// shouldPromptForSurvey decides if we should prompt the given user to take the
// survey. It returns the DeveloperSurveyConfig if we should prompt, and
// undefined if we should not prompt.
export function shouldPromptForSurvey(now: Date, cfg: DeveloperSurveyConfig): DeveloperSurveyConfig | undefined {
// TODO(rstambler): Merge checks for surveys into a setting.
// Don't prompt if the survey hasn't started or is over.
if (!inDateRange(startDate, endDate, now)) {
return;
}
// Reset the values if we're outside of the previous survey period.
if (cfg.datePromptComputed && !inDateRange(startDate, endDate, cfg.datePromptComputed)) {
cfg = {};
}
// If the prompt value is undefined, then this is the first activation
// for this survey period, so decide if we should prompt the user. This
// is done by generating a random number in the range [0, 1) and checking
// if it is < probability.
if (cfg.prompt === undefined) {
const probability = 0.1;
cfg.datePromptComputed = now;
cfg.prompt = Math.random() < probability;
}
flushSurveyConfig(developerSurveyConfig, cfg);
if (!cfg.prompt) {
return;
}
// Check if the user has taken the survey in the current survey period.
// Don't prompt them if they have.
if (cfg.lastDateAccepted) {
if (inDateRange(startDate, endDate, cfg.lastDateAccepted)) {
return;
}
}
// Check if the user has been prompted for the survey in the last 5 days.
// Don't prompt them if they have been.
if (cfg.lastDatePrompted) {
const daysSinceLastPrompt = daysBetween(now, cfg.lastDatePrompted);
// Don't prompt twice on the same day, even if it's the last day of the
// survey.
if (daysSinceLastPrompt < 1) {
return;
}
// If the survey will end in 5 days, prompt on the next day.
// Otherwise, wait for 5 days.
if (daysBetween(now, endDate) > 5) {
return;
}
}
return cfg;
}
export async function promptForDeveloperSurvey(cfg: DeveloperSurveyConfig, now: Date): Promise<DeveloperSurveyConfig> {
const selected = await vscode.window.showInformationMessage(
`Help shape Go’s future! Would you like to help ensure that Go is meeting your needs
by participating in this 10-minute Go Developer Survey (September 2024) before ${endDate.toDateString()}?`,
'Yes',
'Remind me later',
'Never'
);
// Update the time last asked.
cfg.lastDatePrompted = now;
cfg.datePromptComputed = now;
switch (selected) {
case 'Yes':
{
cfg.lastDateAccepted = now;
cfg.prompt = true;
const surveyURL = 'https://google.qualtrics.com/jfe/form/SV_ei0CDV2K9qQIsp8?s=p';
await vscode.env.openExternal(vscode.Uri.parse(surveyURL));
}
break;
case 'Remind me later':
cfg.prompt = true;
vscode.window.showInformationMessage("No problem! We'll ask you again another time.");
break;
case 'Never': {
cfg.prompt = false;
const selected = await vscode.window.showInformationMessage(
`No problem! We won't ask again.
If you'd like to opt-out of all survey prompts, you can set 'go.survey.prompt' to false.`,
'Open Settings'
);
switch (selected) {
case 'Open Settings':
vscode.commands.executeCommand('workbench.action.openSettings', 'go.survey.prompt');
break;
default:
break;
}
break;
}
default:
// If the user closes the prompt without making a selection, treat it
// like a "Not now" response.
cfg.prompt = true;
break;
}
return cfg;
}
export const developerSurveyConfig = 'developerSurveyConfig';
export function getDeveloperSurveyConfig(): DeveloperSurveyConfig {
return getStateConfig(developerSurveyConfig) as DeveloperSurveyConfig;
}
// Assumes that end > start.
export function inDateRange(start: Date, end: Date, date: Date): boolean {
// date is before the start time.
if (date.getTime() - start.getTime() < 0) {
return false;
}
// end is before the date.
if (end.getTime() - date.getTime() < 0) {
return false;
}
return true;
}