blob: ef61afbddead73936d53cf8697c4c86e40a1422e [file] [log] [blame]
/* 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';
import { getGoVersion } from './util';
// Start and end dates of the survey.
export const startDate = new Date('May 31 2022 00:00:00 GMT');
export const endDate = new Date('June 21 2022 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(
// TODO(rstambler): Figure out how to phrase this.
`Looks like you are coding in Go! Would you like to help ensure that Go is meeting your needs
by participating in this 10-minute survey 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 goV = await getGoVersion();
const goVersion = goV ? goV.format(true) : 'na';
const useGopls = getGoConfig()?.get('useLanguageServer') === true ? 'true' : 'false';
const surveyURL = `https://google.qualtrics.com/jfe/form/SV_7O3x4IZKiUn0QCO?s=p&go=${goVersion}&gopls=${useGopls}`;
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;
}