mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Fix extension recommendations (#14275)
* Fix extension recommendations * revert header
This commit is contained in:
@@ -3,12 +3,10 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* 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 { IProductService } from 'vs/platform/product/common/productService';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
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 { 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 { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
import { IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||||
@@ -28,21 +26,16 @@ export class ScenarioRecommendations extends ExtensionRecommendations {
|
|||||||
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
|
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
promptedExtensionRecommendations?: PromptedExtensionRecommendations,
|
|
||||||
@IProductService private readonly productService?: IProductService,
|
@IProductService private readonly productService?: IProductService,
|
||||||
@IInstantiationService private readonly instantiationService?: IInstantiationService,
|
@IInstantiationService private readonly instantiationService?: IInstantiationService,
|
||||||
@IConfigurationService configurationService?: IConfigurationService,
|
|
||||||
@INotificationService private readonly notificationService?: INotificationService,
|
@INotificationService private readonly notificationService?: INotificationService,
|
||||||
@ITelemetryService telemetryService?: ITelemetryService,
|
|
||||||
@IStorageService private readonly storageService?: IStorageService,
|
@IStorageService private readonly storageService?: IStorageService,
|
||||||
@IExtensionManagementService protected readonly extensionManagementService?: IExtensionManagementService,
|
@IExtensionManagementService protected readonly extensionManagementService?: IExtensionManagementService,
|
||||||
@IAdsTelemetryService private readonly adsTelemetryService?: IAdsTelemetryService,
|
@IAdsTelemetryService private readonly adsTelemetryService?: IAdsTelemetryService,
|
||||||
@IExtensionsWorkbenchService protected readonly extensionsWorkbenchService?: IExtensionsWorkbenchService
|
@IExtensionsWorkbenchService protected readonly extensionsWorkbenchService?: IExtensionsWorkbenchService
|
||||||
|
|
||||||
) {
|
) {
|
||||||
super(promptedExtensionRecommendations);
|
super();
|
||||||
|
|
||||||
// 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' }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async doActivate(): Promise<void> {
|
protected async doActivate(): Promise<void> {
|
||||||
@@ -128,7 +121,7 @@ export class ScenarioRecommendations extends ExtensionRecommendations {
|
|||||||
if (!scenarioType) {
|
if (!scenarioType) {
|
||||||
return Promise.reject(new Error(localize('scenarioTypeUndefined', 'The scenario type for extension recommendations must be provided.')));
|
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 => (<IExtensionRecommendation>{ extensionId, sources: ['application'] }));
|
.map(extensionId => (<IExtensionRecommendation>{ extensionId, sources: ['application'] }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* 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 { IProductService } from 'vs/platform/product/common/productService';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||||
@@ -14,10 +14,9 @@ export class StaticRecommendations extends ExtensionRecommendations {
|
|||||||
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
|
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
promptedExtensionRecommendations?: PromptedExtensionRecommendations,
|
|
||||||
@IProductService productService?: IProductService
|
@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' }));
|
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' }));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ export class ConfigBasedRecommendations extends ExtensionRecommendations {
|
|||||||
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
|
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
|
||||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||||
) {
|
) {
|
||||||
// {{SQL CARBON EDIT}}
|
super();
|
||||||
super(undefined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async doActivate(): Promise<void> {
|
protected async doActivate(): Promise<void> {
|
||||||
|
|||||||
@@ -4,35 +4,11 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
import { IExtensionRecommendationReason, EnablementState, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
import { IExtensionRecommendationReson } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||||
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';
|
|
||||||
|
|
||||||
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 = {
|
export type ExtensionRecommendation = {
|
||||||
readonly extensionId: string,
|
readonly extensionId: string,
|
||||||
readonly reason: IExtensionRecommendationReason;
|
readonly reason: IExtensionRecommendationReson;
|
||||||
};
|
};
|
||||||
|
|
||||||
export abstract class ExtensionRecommendations extends Disposable {
|
export abstract class ExtensionRecommendations extends Disposable {
|
||||||
@@ -40,13 +16,6 @@ export abstract class ExtensionRecommendations extends Disposable {
|
|||||||
readonly abstract recommendations: ReadonlyArray<ExtensionRecommendation>;
|
readonly abstract recommendations: ReadonlyArray<ExtensionRecommendation>;
|
||||||
protected abstract doActivate(): Promise<void>;
|
protected abstract doActivate(): Promise<void>;
|
||||||
|
|
||||||
constructor(
|
|
||||||
// {{SQL CARBON EDIT}}
|
|
||||||
protected readonly promptedExtensionRecommendations?: PromptedExtensionRecommendations,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _activationPromise: Promise<void> | null = null;
|
private _activationPromise: Promise<void> | null = null;
|
||||||
get activated(): boolean { return this._activationPromise !== null; }
|
get activated(): boolean { return this._activationPromise !== null; }
|
||||||
activate(): Promise<void> {
|
activate(): Promise<void> {
|
||||||
@@ -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<void> {
|
|
||||||
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<void> {
|
|
||||||
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<IExtensionsConfiguration>(ConfigurationKey);
|
|
||||||
return config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasToIgnoreWorkspaceRecommendationNotifications(): boolean {
|
|
||||||
return this.hasToIgnoreRecommendationNotifications() || this.storageService.getBoolean(ignoreWorkspaceRecommendationsStorageKey, StorageScope.WORKSPACE, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
filterIgnoredOrNotAllowed(recommendationsToSuggest: string[]): string[] {
|
|
||||||
const importantRecommendationsIgnoreList = (<string[]>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<IExtension[]> {
|
|
||||||
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<void> {
|
|
||||||
try {
|
|
||||||
await action.run();
|
|
||||||
} finally {
|
|
||||||
action.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private addToImportantRecommendationsIgnore(id: string) {
|
|
||||||
const importantRecommendationsIgnoreList = <string[]>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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user