From 34aa77eea373d5820bee8b863d9b54adb115081f Mon Sep 17 00:00:00 2001 From: Karl Burtram Date: Fri, 18 Feb 2022 15:57:39 -0800 Subject: [PATCH] Fix opt out prompt behavior (#18498) (#18499) * fix header * fix product name Co-authored-by: Aditya Bist --- .../browser/telemetryOptOut.contribution.ts | 11 ++ .../browser/telemetryOptOut.ts | 184 ++++++++++++++++++ .../telemetryOptOut.contribution.ts | 11 ++ .../electron-sandbox/telemetryOptOut.ts | 44 +++++ src/vs/workbench/workbench.sandbox.main.ts | 4 + src/vs/workbench/workbench.web.main.ts | 3 + 6 files changed, 257 insertions(+) create mode 100644 src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.contribution.ts create mode 100644 src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts create mode 100644 src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.contribution.ts create mode 100644 src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.ts diff --git a/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.contribution.ts b/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.contribution.ts new file mode 100644 index 0000000000..b08ede73f6 --- /dev/null +++ b/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.contribution.ts @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Registry } from 'vs/platform/registry/common/platform'; +import { BrowserTelemetryOptOut } from 'vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BrowserTelemetryOptOut, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts new file mode 100644 index 0000000000..d5f52b3433 --- /dev/null +++ b/src/vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.ts @@ -0,0 +1,184 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { IExperimentService, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { language, locale } from 'vs/base/common/platform'; +import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; + +export abstract class AbstractTelemetryOptOut implements IWorkbenchContribution { + + private static readonly TELEMETRY_OPT_OUT_SHOWN = 'workbench.telemetryOptOutShown'; + private privacyUrl: string | undefined; + + constructor( + @IStorageService private readonly storageService: IStorageService, + @IOpenerService private readonly openerService: IOpenerService, + @INotificationService private readonly notificationService: INotificationService, + @IHostService private readonly hostService: IHostService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IExperimentService private readonly experimentService: IExperimentService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IProductService private readonly productService: IProductService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IJSONEditingService private readonly jsonEditingService: IJSONEditingService + ) { + } + + protected async handleTelemetryOptOut(): Promise { + if (this.productService.telemetryOptOutUrl && !this.storageService.get(AbstractTelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, StorageScope.GLOBAL)) { + const experimentId = 'telemetryOptOut'; + + const [count, experimentState] = await Promise.all([this.getWindowCount(), this.experimentService.getExperimentById(experimentId)]); + + if (!this.hostService.hasFocus && count > 1) { + return; // return early if meanwhile another window opened (we only show the opt-out once) + } + + this.storageService.store(AbstractTelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, true, StorageScope.GLOBAL, StorageTarget.USER); + + this.privacyUrl = this.productService.privacyStatementUrl || this.productService.telemetryOptOutUrl; + + if (experimentState && experimentState.state === ExperimentState.Run && this.telemetryService.isOptedIn) { + this.runExperiment(experimentId); + return; + } + + const telemetryOptOutUrl = this.productService.telemetryOptOutUrl; + if (telemetryOptOutUrl) { + this.showTelemetryOptOut(telemetryOptOutUrl); + } + } + } + + private showTelemetryOptOut(telemetryOptOutUrl: string): void { + const optOutNotice = localize('telemetryOptOut.optOutNotice', "Help improve Azure Data Studio by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt out]({1}).", this.privacyUrl, this.productService.telemetryOptOutUrl); + const optInNotice = localize('telemetryOptOut.optInNotice', "Help improve Azure Data Studio by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt in]({1}).", this.privacyUrl, this.productService.telemetryOptOutUrl); + + this.notificationService.prompt( + Severity.Info, + this.telemetryService.isOptedIn ? optOutNotice : optInNotice, + [{ + label: localize('telemetryOptOut.readMore', "Read More"), + run: () => this.openerService.open(URI.parse(telemetryOptOutUrl)) + }], + { sticky: true } + ); + } + + protected abstract getWindowCount(): Promise; + + private runExperiment(experimentId: string) { + const promptMessageKey = 'telemetryOptOut.optOutOption'; + const yesLabelKey = 'telemetryOptOut.OptIn'; + const noLabelKey = 'telemetryOptOut.OptOut'; + + let promptMessage = localize('telemetryOptOut.optOutOption', "Please help Microsoft improve Visual Studio Code by allowing the collection of usage data. Read our [privacy statement]({0}) for more details.", this.privacyUrl); + let yesLabel = localize('telemetryOptOut.OptIn', "Yes, glad to help"); + let noLabel = localize('telemetryOptOut.OptOut', "No, thanks"); + + let queryPromise = Promise.resolve(undefined); + if (locale && locale !== language && locale !== 'en' && locale.indexOf('en-') === -1) { + queryPromise = this.galleryService.query({ text: `tag:lp-${locale}` }, CancellationToken.None).then(tagResult => { + if (!tagResult || !tagResult.total) { + return undefined; + } + const extensionToFetchTranslationsFrom = tagResult.firstPage.filter(e => e.publisher === 'MS-CEINTL' && e.name.indexOf('vscode-language-pack') === 0)[0] || tagResult.firstPage[0]; + if (!extensionToFetchTranslationsFrom.assets || !extensionToFetchTranslationsFrom.assets.coreTranslations.length) { + return undefined; + } + + return this.galleryService.getCoreTranslation(extensionToFetchTranslationsFrom, locale!) + .then(translation => { + const translationsFromPack: any = translation && translation.contents ? translation.contents['vs/workbench/contrib/welcome/telemetryOptOut/electron-browser/telemetryOptOut'] : {}; + if (!!translationsFromPack[promptMessageKey] && !!translationsFromPack[yesLabelKey] && !!translationsFromPack[noLabelKey]) { + promptMessage = translationsFromPack[promptMessageKey].replace('{0}', this.privacyUrl) + ' (Please help Microsoft improve Visual Studio Code by allowing the collection of usage data.)'; + yesLabel = translationsFromPack[yesLabelKey] + ' (Yes)'; + noLabel = translationsFromPack[noLabelKey] + ' (No)'; + } + return undefined; + }); + + }); + } + + const logTelemetry = (optout?: boolean) => { + type ExperimentsOptOutClassification = { + optout?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + }; + + type ExperimentsOptOutEvent = { + optout?: boolean; + }; + this.telemetryService.publicLog2('experiments:optout', typeof optout === 'boolean' ? { optout } : {}); + }; + + queryPromise.then(() => { + this.notificationService.prompt( + Severity.Info, + promptMessage, + [ + { + label: yesLabel, + run: () => { + logTelemetry(false); + } + }, + { + label: noLabel, + run: async () => { + logTelemetry(true); + this.configurationService.updateValue('telemetry.enableTelemetry', false); + await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['enable-crash-reporter'], value: false }], true); + } + } + ], + { + sticky: true, + onCancel: logTelemetry + } + ); + this.experimentService.markAsCompleted(experimentId); + }); + } +} + +export class BrowserTelemetryOptOut extends AbstractTelemetryOptOut { + + constructor( + @IStorageService storageService: IStorageService, + @IOpenerService openerService: IOpenerService, + @INotificationService notificationService: INotificationService, + @IHostService hostService: IHostService, + @ITelemetryService telemetryService: ITelemetryService, + @IExperimentService experimentService: IExperimentService, + @IConfigurationService configurationService: IConfigurationService, + @IExtensionGalleryService galleryService: IExtensionGalleryService, + @IProductService productService: IProductService, + @IEnvironmentService environmentService: IEnvironmentService, + @IJSONEditingService jsonEditingService: IJSONEditingService + ) { + super(storageService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService, environmentService, jsonEditingService); + + this.handleTelemetryOptOut(); + } + + protected async getWindowCount(): Promise { + return 1; + } +} diff --git a/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.contribution.ts b/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.contribution.ts new file mode 100644 index 0000000000..a347377e40 --- /dev/null +++ b/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.contribution.ts @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { NativeTelemetryOptOut } from 'vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut'; + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NativeTelemetryOptOut, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.ts new file mode 100644 index 0000000000..3f87a2180c --- /dev/null +++ b/src/vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { AbstractTelemetryOptOut } from 'vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; + +export class NativeTelemetryOptOut extends AbstractTelemetryOptOut { + + constructor( + @IStorageService storageService: IStorageService, + @IOpenerService openerService: IOpenerService, + @INotificationService notificationService: INotificationService, + @IHostService hostService: IHostService, + @ITelemetryService telemetryService: ITelemetryService, + @IExperimentService experimentService: IExperimentService, + @IConfigurationService configurationService: IConfigurationService, + @IExtensionGalleryService galleryService: IExtensionGalleryService, + @IProductService productService: IProductService, + @IEnvironmentService environmentService: IEnvironmentService, + @IJSONEditingService jsonEditingService: IJSONEditingService, + @INativeHostService private readonly nativeHostService: INativeHostService + ) { + super(storageService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService, environmentService, jsonEditingService); + + this.handleTelemetryOptOut(); + } + + protected getWindowCount(): Promise { + return this.nativeHostService.getWindowCount(); + } +} diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index f2aeda4625..c0e6ea9269 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -142,4 +142,8 @@ import 'vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal. // Webview import 'vs/workbench/contrib/webview/electron-sandbox/webview.contribution'; +// {{SQL CARBON EDIT}} +// Telemetry Opt Out +import 'vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut.contribution'; + //#endregion diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index da3fa3ebda..4f6eba49de 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -159,4 +159,7 @@ import 'vs/workbench/contrib/issue/browser/issue.web.contribution'; // Getting Started import 'sql/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution'; +// Telemetry Opt Out +import 'vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut.contribution'; + //#endregion