#136301 - support whenNotInstalled for config based and exe recommendations

This commit is contained in:
Sandeep Somavarapu 2022-02-11 14:24:01 +01:00
parent a64e8e5673
commit a0a9e5dbd3
No known key found for this signature in database
GPG key ID: 1FED25EC4646638B
6 changed files with 37 additions and 16 deletions

View file

@ -165,14 +165,14 @@ export interface IConfigBasedExtensionTip {
configPath: string;
configName: string;
configScheme?: string;
recommendations: IStringDictionary<{ name: string; remotes?: string[]; important?: boolean; isExtensionPack?: boolean }>;
recommendations: IStringDictionary<{ name: string; remotes?: string[]; important?: boolean; isExtensionPack?: boolean; whenNotInstalled?: string[] }>;
}
export interface IExeBasedExtensionTip {
friendlyName: string;
windowsPath?: string;
important?: boolean;
recommendations: IStringDictionary<{ name: string; important?: boolean; isExtensionPack?: boolean }>;
recommendations: IStringDictionary<{ name: string; important?: boolean; isExtensionPack?: boolean; whenNotInstalled?: string[] }>;
}
export interface IRemoteExtensionTip {

View file

@ -465,6 +465,7 @@ export type IConfigBasedExtensionTip = {
readonly isExtensionPack: boolean;
readonly configName: string;
readonly important: boolean;
readonly whenNotInstalled?: string[];
};
export type IExecutableBasedExtensionTip = {
@ -474,6 +475,7 @@ export type IExecutableBasedExtensionTip = {
readonly exeName: string;
readonly exeFriendlyName: string;
readonly windowsPath?: string;
readonly whenNotInstalled?: string[];
};
export type IWorkspaceTips = { readonly remoteSet: string[]; readonly recommendations: string[] };

View file

@ -68,7 +68,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
extensionName: value.name,
configName: tip.configName,
important: !!value.important,
isExtensionPack: !!value.isExtensionPack
isExtensionPack: !!value.isExtensionPack,
whenNotInstalled: value.whenNotInstalled
});
}
} else {
@ -77,7 +78,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
extensionName: value.name,
configName: tip.configName,
important: !!value.important,
isExtensionPack: !!value.isExtensionPack
isExtensionPack: !!value.isExtensionPack,
whenNotInstalled: value.whenNotInstalled
});
}
});

View file

@ -14,8 +14,10 @@ import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { IExecutableBasedExtensionTip, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionTipsService as BaseExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionTipsService';
import { IExtensionRecommendationNotificationService, RecommendationsNotificationResult, RecommendationSource } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
@ -32,7 +34,7 @@ type ExeExtensionRecommendationsClassification = {
type IExeBasedExtensionTips = {
readonly exeFriendlyName: string;
readonly windowsPath?: string;
readonly recommendations: { extensionId: string; extensionName: string; isExtensionPack: boolean }[];
readonly recommendations: { extensionId: string; extensionName: string; isExtensionPack: boolean; whenNotInstalled?: string[] }[];
};
const promptedExecutableTipsStorageKey = 'extensionTips/promptedExecutableTips';
@ -242,8 +244,11 @@ export class ExtensionTipsService extends BaseExtensionTipsService {
});
}
private promptExeRecommendations(tips: IExecutableBasedExtensionTip[]): Promise<RecommendationsNotificationResult> {
const extensionIds = tips.map(({ extensionId }) => extensionId.toLowerCase());
private async promptExeRecommendations(tips: IExecutableBasedExtensionTip[]): Promise<RecommendationsNotificationResult> {
const installed = await this.extensionManagementService.getInstalled(ExtensionType.User);
const extensionIds = tips
.filter(tip => !tip.whenNotInstalled || tip.whenNotInstalled.every(id => installed.every(local => !areSameExtensions(local.identifier, { id }))))
.map(({ extensionId }) => extensionId.toLowerCase());
const message = localize({ key: 'exeRecommended', comment: ['Placeholder string is the name of the software that is installed.'] }, "You have {0} installed on your system. Do you want to install the recommended extensions for it?", tips[0].exeFriendlyName);
return this.extensionRecommendationNotificationService.promptImportantExtensionsInstallNotification(extensionIds, message, `@exe:"${tips[0].exeName}"`, RecommendationSource.EXE);
}
@ -316,7 +321,7 @@ export class ExtensionTipsService extends BaseExtensionTipsService {
checkedExecutables.set(exePath, exists);
}
if (exists) {
for (const { extensionId, extensionName, isExtensionPack } of extensionTip.recommendations) {
for (const { extensionId, extensionName, isExtensionPack, whenNotInstalled } of extensionTip.recommendations) {
result.push({
extensionId,
extensionName,
@ -324,6 +329,7 @@ export class ExtensionTipsService extends BaseExtensionTipsService {
exeName,
exeFriendlyName: extensionTip.exeFriendlyName,
windowsPath: extensionTip.windowsPath,
whenNotInstalled: whenNotInstalled
});
}
}

View file

@ -10,6 +10,8 @@ import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRe
import { IWorkspaceContextService, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { Emitter } from 'vs/base/common/event';
export type ConfigBasedExtensionRecommendation = ExtensionRecommendation & { whenNotInstalled: string[] | undefined };
export class ConfigBasedRecommendations extends ExtensionRecommendations {
private importantTips: IConfigBasedExtensionTip[] = [];
@ -18,13 +20,13 @@ export class ConfigBasedRecommendations extends ExtensionRecommendations {
private _onDidChangeRecommendations = this._register(new Emitter<void>());
readonly onDidChangeRecommendations = this._onDidChangeRecommendations.event;
private _otherRecommendations: ExtensionRecommendation[] = [];
get otherRecommendations(): ReadonlyArray<ExtensionRecommendation> { return this._otherRecommendations; }
private _otherRecommendations: ConfigBasedExtensionRecommendation[] = [];
get otherRecommendations(): ReadonlyArray<ConfigBasedExtensionRecommendation> { return this._otherRecommendations; }
private _importantRecommendations: ExtensionRecommendation[] = [];
get importantRecommendations(): ReadonlyArray<ExtensionRecommendation> { return this._importantRecommendations; }
private _importantRecommendations: ConfigBasedExtensionRecommendation[] = [];
get importantRecommendations(): ReadonlyArray<ConfigBasedExtensionRecommendation> { return this._importantRecommendations; }
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return [...this.importantRecommendations, ...this.otherRecommendations]; }
get recommendations(): ReadonlyArray<ConfigBasedExtensionRecommendation> { return [...this.importantRecommendations, ...this.otherRecommendations]; }
constructor(
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
@ -69,13 +71,14 @@ export class ConfigBasedRecommendations extends ExtensionRecommendations {
}
}
private toExtensionRecommendation(tip: IConfigBasedExtensionTip): ExtensionRecommendation {
private toExtensionRecommendation(tip: IConfigBasedExtensionTip): ConfigBasedExtensionRecommendation {
return {
extensionId: tip.extensionId,
reason: {
reasonId: ExtensionRecommendationReason.WorkspaceConfig,
reasonText: localize('exeBasedRecommendation', "This extension is recommended because of the current workspace configuration")
}
},
whenNotInstalled: tip.whenNotInstalled
};
}

View file

@ -25,6 +25,8 @@ import { IExtensionRecommendationNotificationService } from 'vs/platform/extensi
import { timeout } from 'vs/base/common/async';
import { URI } from 'vs/base/common/uri';
import { WebRecommendations } from 'vs/workbench/contrib/extensions/browser/webRecommendations';
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
type IgnoreRecommendationClassification = {
recommendationReason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
@ -61,6 +63,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionIgnoredRecommendationsService private readonly extensionRecommendationsManagementService: IExtensionIgnoredRecommendationsService,
@IExtensionRecommendationNotificationService private readonly extensionRecommendationNotificationService: IExtensionRecommendationNotificationService,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
) {
super();
@ -260,7 +263,12 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
}
private async promptWorkspaceRecommendations(): Promise<void> {
const allowedRecommendations = [...this.workspaceRecommendations.recommendations, ...this.configBasedRecommendations.importantRecommendations]
const installed = await this.extensionsWorkbenchService.queryLocal();
const allowedRecommendations = [
...this.workspaceRecommendations.recommendations,
...this.configBasedRecommendations.importantRecommendations.filter(
recommendation => !recommendation.whenNotInstalled || recommendation.whenNotInstalled.every(id => installed.every(local => !areSameExtensions(local.identifier, { id }))))
]
.map(({ extensionId }) => extensionId)
.filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId));