diff --git a/src/extension.ts b/src/extension.ts index c7bbd8e941..7df4266d75 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -32,6 +32,7 @@ import { addDockerSettingsToEnv } from './utils/addDockerSettingsToEnv'; import { addUserAgent } from './utils/addUserAgent'; import { getTrustedCertificates } from './utils/getTrustedCertificates'; import { Keytar } from './utils/keytar'; +import { nps } from './utils/nps'; import { DefaultTerminalProvider } from './utils/TerminalProvider'; import { tryGetDefaultDockerContext } from './utils/tryGetDefaultDockerContext'; import { wrapError } from './utils/wrapError'; @@ -131,6 +132,10 @@ export async function activateInternal(ctx: vscode.ExtensionContext, perfStats: activateLanguageClient(ctx); registerListeners(ctx); + + // Don't wait + // tslint:disable-next-line: no-floating-promises + nps(ctx.globalState); }); } diff --git a/src/utils/nps.ts b/src/utils/nps.ts new file mode 100644 index 0000000000..b793080891 --- /dev/null +++ b/src/utils/nps.ts @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Loosely adapted from https://github.com/microsoft/vscode-azure-account/blob/2f497562cab5f3db09f983ab5101040f27dceb70/src/nps.ts + +import { env, Memento, Uri, window } from "vscode"; +import { ext } from "vscode-azureappservice/out/src/extensionVariables"; + +const PROBABILITY = 0.15; +const MIN_SESSION_COUNT = 10; + +const SURVEY_NAME = 'nps1'; +const SURVEY_URL = 'https://aka.ms/vscodedockernpsinproduct'; + +const SESSION_COUNT_KEY = `${SURVEY_NAME}/sessioncount`; +const LAST_SESSION_DATE_KEY = `${SURVEY_NAME}/lastsessiondate`; +const IS_CANDIDATE_KEY = `${SURVEY_NAME}/iscandidate`; + +export async function nps(globalState: Memento): Promise { + try { + // If not English-language, don't ask + if (env.language !== 'en' && !env.language.startsWith('en-')) { + return; + } + + let isCandidate: boolean | undefined = globalState.get(IS_CANDIDATE_KEY); + + // If not a candidate, don't ask + if (isCandidate === false) { + return; + } + + const date = new Date().toDateString(); + const lastSessionDate = globalState.get(LAST_SESSION_DATE_KEY, new Date(0).toDateString()); + + // If this session is on same date as last session, don't count it + if (date === lastSessionDate) { + return; + } + + // Count this session + const sessionCount = globalState.get(SESSION_COUNT_KEY, 0) + 1; + await globalState.update(LAST_SESSION_DATE_KEY, date); + await globalState.update(SESSION_COUNT_KEY, sessionCount); + + // If under the MIN_SESSION_COUNT, don't ask + if (sessionCount < MIN_SESSION_COUNT) { + return; + } + + // Decide if they are a candidate (if we previously decided they are and they did Remind Me Later, we will not do probability again) + // i.e. Probability only comes into play if isCandidate is undefined + // tslint:disable-next-line: insecure-random + isCandidate = isCandidate || Math.random() < PROBABILITY; + await globalState.update(IS_CANDIDATE_KEY, isCandidate); + + // If not a candidate, don't ask + if (!isCandidate) { + return; + } + + const take = { title: 'Take Survey', telName: 'take' }; + const remind = { title: 'Remind Me Later', telName: 'remind' }; + const never = { title: 'Don\'t Show Again', telName: 'never' }; + + // Prompt, treating hitting X as Remind Me Later + const result = (await window.showInformationMessage('Do you mind taking a quick feedback survey about the Docker Extension for VS Code?', take, remind, never)) || remind; + + ext.reporter.sendTelemetryEvent('nps', { survey: SURVEY_NAME, response: result.telName }); + + if (result === take) { + // If they hit Take, don't ask again (for this survey name), and open the survey + await globalState.update(IS_CANDIDATE_KEY, false); + await env.openExternal(Uri.parse(`${SURVEY_URL}?o=${encodeURIComponent(process.platform)}&m=${encodeURIComponent(env.machineId)}`)); + } else if (result === remind) { + // If they hit the X or Remind Me Later, ask again in 3 sessions + await globalState.update(SESSION_COUNT_KEY, MIN_SESSION_COUNT - 3); + } else if (result === never) { + // If they hit Never, don't ask again (for this survey name) + await globalState.update(IS_CANDIDATE_KEY, false); + } + } catch { } // Best effort +}