diff --git a/src/sql/workbench/contrib/extensions/browser/scenarioRecommendations.ts b/src/sql/workbench/contrib/extensions/browser/scenarioRecommendations.ts index a905109970..6dcddcd39c 100644 --- a/src/sql/workbench/contrib/extensions/browser/scenarioRecommendations.ts +++ b/src/sql/workbench/contrib/extensions/browser/scenarioRecommendations.ts @@ -3,12 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionRecommendations, ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; +import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; import { IProductService } from 'vs/platform/product/common/productService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { localize } from 'vs/nls'; import { IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -28,21 +26,16 @@ export class ScenarioRecommendations extends ExtensionRecommendations { get recommendations(): ReadonlyArray { return this._recommendations; } constructor( - promptedExtensionRecommendations?: PromptedExtensionRecommendations, @IProductService private readonly productService?: IProductService, @IInstantiationService private readonly instantiationService?: IInstantiationService, - @IConfigurationService configurationService?: IConfigurationService, @INotificationService private readonly notificationService?: INotificationService, - @ITelemetryService telemetryService?: ITelemetryService, @IStorageService private readonly storageService?: IStorageService, @IExtensionManagementService protected readonly extensionManagementService?: IExtensionManagementService, @IAdsTelemetryService private readonly adsTelemetryService?: IAdsTelemetryService, @IExtensionsWorkbenchService protected readonly extensionsWorkbenchService?: IExtensionsWorkbenchService ) { - super(promptedExtensionRecommendations); - - // this._recommendations = productService.recommendedExtensionsByScenario.map(r => ({ extensionId: r, reason: { reasonId: ExtensionRecommendationReason.Application, reasonText: localize('defaultRecommendations', "This extension is recommended by Azure Data Studio.") }, source: 'application' })); + super(); } protected async doActivate(): Promise { @@ -128,7 +121,7 @@ export class ScenarioRecommendations extends ExtensionRecommendations { if (!scenarioType) { return Promise.reject(new Error(localize('scenarioTypeUndefined', 'The scenario type for extension recommendations must be provided.'))); } - return this.promptedExtensionRecommendations.filterIgnoredOrNotAllowed(this.productService.recommendedExtensionsByScenario[scenarioType] || []) + return (this.productService.recommendedExtensionsByScenario[scenarioType] || []) .map(extensionId => ({ extensionId, sources: ['application'] })); } } diff --git a/src/sql/workbench/contrib/extensions/browser/staticRecommendations.ts b/src/sql/workbench/contrib/extensions/browser/staticRecommendations.ts index 6aa9f4acc1..28b4bacfb4 100644 --- a/src/sql/workbench/contrib/extensions/browser/staticRecommendations.ts +++ b/src/sql/workbench/contrib/extensions/browser/staticRecommendations.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionRecommendations, ExtensionRecommendation, PromptedExtensionRecommendations } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; +import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations'; import { IProductService } from 'vs/platform/product/common/productService'; import { localize } from 'vs/nls'; import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; @@ -14,10 +14,9 @@ export class StaticRecommendations extends ExtensionRecommendations { get recommendations(): ReadonlyArray { return this._recommendations; } constructor( - promptedExtensionRecommendations?: PromptedExtensionRecommendations, @IProductService productService?: IProductService ) { - super(promptedExtensionRecommendations); + super(); this._recommendations = productService.recommendedExtensions.map(r => ({ extensionId: r, reason: { reasonId: ExtensionRecommendationReason.Application, reasonText: localize('defaultRecommendations', "This extension is recommended by Azure Data Studio.") }, source: 'application' })); } diff --git a/src/vs/workbench/contrib/extensions/browser/configBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/configBasedRecommendations.ts index 2adf6a4098..930f4c48ed 100644 --- a/src/vs/workbench/contrib/extensions/browser/configBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/configBasedRecommendations.ts @@ -30,8 +30,7 @@ export class ConfigBasedRecommendations extends ExtensionRecommendations { @IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, ) { - // {{SQL CARBON EDIT}} - super(undefined); + super(); } protected async doActivate(): Promise { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendations.ts index 7a2a444a92..65eadd0621 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendations.ts @@ -4,35 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { IExtensionRecommendationReason, EnablementState, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { localize } from 'vs/nls'; -import { SearchExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import { IExtensionsConfiguration, ConfigurationKey, IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IAction } from 'vs/base/common/actions'; -import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IExtensionRecommendationReson } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; -type ExtensionRecommendationsNotificationClassification = { - userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - extensionId?: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; -}; - -type ExtensionWorkspaceRecommendationsNotificationClassification = { - userReaction: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; -}; - -const ignoreWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; -const ignoreImportantExtensionRecommendation = 'extensionsAssistant/importantRecommendationsIgnore'; -const choiceNever = localize('neverShowAgain', "Don't Show Again"); export type ExtensionRecommendation = { readonly extensionId: string, - readonly reason: IExtensionRecommendationReason; + readonly reason: IExtensionRecommendationReson; }; export abstract class ExtensionRecommendations extends Disposable { @@ -40,13 +16,6 @@ export abstract class ExtensionRecommendations extends Disposable { readonly abstract recommendations: ReadonlyArray; protected abstract doActivate(): Promise; - constructor( - // {{SQL CARBON EDIT}} - protected readonly promptedExtensionRecommendations?: PromptedExtensionRecommendations, - ) { - super(); - } - private _activationPromise: Promise | null = null; get activated(): boolean { return this._activationPromise !== null; } activate(): Promise { @@ -57,191 +26,3 @@ export abstract class ExtensionRecommendations extends Disposable { } } - -export class PromptedExtensionRecommendations extends Disposable { - - constructor( - private readonly isExtensionAllowedToBeRecommended: (extensionId: string) => boolean, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @INotificationService private readonly notificationService: INotificationService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - @IStorageService private readonly storageService: IStorageService, - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, - ) { - super(); - } - - async promptImportantExtensionsInstallNotification(extensionIds: string[], message: string, searchValue: string): Promise { - if (this.hasToIgnoreRecommendationNotifications()) { - return; - } - - const extensions = await this.getInstallableExtensions(extensionIds); - if (!extensions.length) { - return; - } - - this.notificationService.prompt(Severity.Info, message, - [{ - label: localize('install', "Install"), - run: async () => { - this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue)); - await Promise.all(extensions.map(async extension => { - this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'install', extensionId: extension.identifier.id }); - this.extensionsWorkbenchService.open(extension, { pinned: true }); - await this.extensionManagementService.installFromGallery(extension.gallery!); - })); - } - }, { - label: localize('show recommendations', "Show Recommendations"), - run: async () => { - for (const extension of extensions) { - this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'show', extensionId: extension.identifier.id }); - this.extensionsWorkbenchService.open(extension, { pinned: true }); - } - this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue)); - } - }, { - label: choiceNever, - isSecondary: true, - run: () => { - for (const extension of extensions) { - this.addToImportantRecommendationsIgnore(extension.identifier.id); - this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: extension.identifier.id }); - } - this.notificationService.prompt( - Severity.Info, - localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?"), - [{ - label: localize('ignoreAll', "Yes, Ignore All"), - run: () => this.setIgnoreRecommendationsConfig(true) - }, { - label: localize('no', "No"), - run: () => this.setIgnoreRecommendationsConfig(false) - }] - ); - } - }], - { - sticky: true, - onCancel: () => { - for (const extension of extensions) { - this.telemetryService.publicLog2<{ userReaction: string, extensionId: string }, ExtensionRecommendationsNotificationClassification>('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId: extension.identifier.id }); - } - } - } - ); - } - - async promptWorkspaceRecommendations(recommendations: string[]): Promise { - if (this.hasToIgnoreWorkspaceRecommendationNotifications()) { - return; - } - - let installed = await this.extensionManagementService.getInstalled(); - installed = installed.filter(l => this.extensionEnablementService.getEnablementState(l) !== EnablementState.DisabledByExtensionKind); // Filter extensions disabled by kind - recommendations = recommendations.filter(extensionId => installed.every(local => !areSameExtensions({ id: extensionId }, local.identifier))); - - if (!recommendations.length) { - return; - } - - const extensions = await this.getInstallableExtensions(recommendations); - if (!extensions.length) { - return; - } - - const searchValue = '@recommended '; - this.notificationService.prompt( - Severity.Info, - localize('workspaceRecommended', "Do you want to install the recommended extensions for this repository?"), - [{ - label: localize('install', "Install"), - run: async () => { - this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'install' }); - await Promise.all(extensions.map(async extension => { - this.extensionsWorkbenchService.open(extension, { pinned: true }); - await this.extensionManagementService.installFromGallery(extension.gallery!); - })); - } - }, { - label: localize('showRecommendations', "Show Recommendations"), - run: async () => { - this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'show' }); - this.runAction(this.instantiationService.createInstance(SearchExtensionsAction, searchValue)); - } - }, { - label: localize('neverShowAgain', "Don't Show Again"), - isSecondary: true, - run: () => { - this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'neverShowAgain' }); - this.storageService.store(ignoreWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); - } - }], - { - sticky: true, - onCancel: () => { - this.telemetryService.publicLog2<{ userReaction: string }, ExtensionWorkspaceRecommendationsNotificationClassification>('extensionWorkspaceRecommendations:popup', { userReaction: 'cancelled' }); - } - } - ); - } - - hasToIgnoreRecommendationNotifications(): boolean { - const config = this.configurationService.getValue(ConfigurationKey); - return config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand; - } - - hasToIgnoreWorkspaceRecommendationNotifications(): boolean { - return this.hasToIgnoreRecommendationNotifications() || this.storageService.getBoolean(ignoreWorkspaceRecommendationsStorageKey, StorageScope.WORKSPACE, false); - } - - filterIgnoredOrNotAllowed(recommendationsToSuggest: string[]): string[] { - const importantRecommendationsIgnoreList = (JSON.parse(this.storageService.get(ignoreImportantExtensionRecommendation, StorageScope.GLOBAL, '[]'))).map(e => e.toLowerCase()); - return recommendationsToSuggest.filter(id => { - if (importantRecommendationsIgnoreList.indexOf(id) !== -1) { - return false; - } - if (!this.isExtensionAllowedToBeRecommended(id)) { - return false; - } - return true; - }); - } - - private async getInstallableExtensions(extensionIds: string[]): Promise { - const extensions: IExtension[] = []; - if (extensionIds.length) { - const pager = await this.extensionsWorkbenchService.queryGallery({ names: extensionIds, pageSize: extensionIds.length, source: 'install-recommendations' }, CancellationToken.None); - for (const extension of pager.firstPage) { - if (extension.gallery && (await this.extensionManagementService.canInstall(extension.gallery))) { - extensions.push(extension); - } - } - } - return extensions; - } - - private async runAction(action: IAction): Promise { - try { - await action.run(); - } finally { - action.dispose(); - } - } - - private addToImportantRecommendationsIgnore(id: string) { - const importantRecommendationsIgnoreList = JSON.parse(this.storageService.get(ignoreImportantExtensionRecommendation, StorageScope.GLOBAL, '[]')); - importantRecommendationsIgnoreList.push(id.toLowerCase()); - this.storageService.store(ignoreImportantExtensionRecommendation, JSON.stringify(importantRecommendationsIgnoreList), StorageScope.GLOBAL, StorageTarget.MACHINE); - } - - private setIgnoreRecommendationsConfig(configVal: boolean) { - this.configurationService.updateValue('extensions.ignoreRecommendations', configVal, ConfigurationTarget.USER); - } - -} -