mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-30 00:30:29 -04:00
Merge from vscode 2e5312cd61ff99c570299ecc122c52584265eda2
This commit is contained in:
committed by
Anthony Dresser
parent
3603f55d97
commit
7f1d8fc32f
@@ -0,0 +1,120 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IExtensionTipsService, IExecutableBasedExtensionTip, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
type ExeExtensionRecommendationsClassification = {
|
||||
extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
|
||||
exeName: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
|
||||
export class ExeBasedRecommendations extends ExtensionRecommendations {
|
||||
|
||||
readonly _recommendations: ExtensionRecommendation[] = [];
|
||||
get recommendations(): ReadonlyArray<ExtensionRecommendation> { return this._recommendations; }
|
||||
|
||||
constructor(
|
||||
isExtensionAllowedToBeRecommended: (extensionId: string) => boolean,
|
||||
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
) {
|
||||
super(isExtensionAllowedToBeRecommended, instantiationService, configurationService, notificationService, telemetryService, storageService, storageKeysSyncRegistryService);
|
||||
|
||||
/*
|
||||
3s has come out to be the good number to fetch and prompt important exe based recommendations
|
||||
Also fetch important exe based recommendations for reporting telemetry
|
||||
*/
|
||||
timeout(3000).then(() => this.fetchAndPromptImportantExeBasedRecommendations());
|
||||
}
|
||||
|
||||
protected async doActivate(): Promise<void> {
|
||||
const otherExectuableBasedTips = await this.extensionTipsService.getOtherExecutableBasedTips();
|
||||
otherExectuableBasedTips.forEach(tip => this._recommendations.push(this.toExtensionRecommendation(tip)));
|
||||
}
|
||||
|
||||
private async fetchAndPromptImportantExeBasedRecommendations(): Promise<void> {
|
||||
const importantExeBasedRecommendations: IStringDictionary<IExecutableBasedExtensionTip> = {};
|
||||
const importantExectuableBasedTips = await this.extensionTipsService.getImportantExecutableBasedTips();
|
||||
importantExectuableBasedTips.forEach(tip => {
|
||||
this._recommendations.push(this.toExtensionRecommendation(tip));
|
||||
importantExeBasedRecommendations[tip.extensionId.toLowerCase()] = tip;
|
||||
});
|
||||
|
||||
const local = await this.extensionManagementService.getInstalled(ExtensionType.User);
|
||||
const { installed, uninstalled } = this.groupByInstalled(Object.keys(importantExeBasedRecommendations), local);
|
||||
|
||||
/* Log installed and uninstalled exe based recommendations */
|
||||
for (const extensionId of installed) {
|
||||
const tip = importantExeBasedRecommendations[extensionId];
|
||||
this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:alreadyInstalled', { extensionId, exeName: basename(tip.windowsPath!) });
|
||||
}
|
||||
for (const extensionId of uninstalled) {
|
||||
const tip = importantExeBasedRecommendations[extensionId];
|
||||
this.telemetryService.publicLog2<{ exeName: string, extensionId: string }, ExeExtensionRecommendationsClassification>('exeExtensionRecommendations:notInstalled', { extensionId, exeName: basename(tip.windowsPath!) });
|
||||
}
|
||||
|
||||
this.promptImportantExeBasedRecommendations(uninstalled, importantExeBasedRecommendations);
|
||||
}
|
||||
|
||||
private promptImportantExeBasedRecommendations(recommendations: string[], importantExeBasedRecommendations: IStringDictionary<IExecutableBasedExtensionTip>): void {
|
||||
if (this.hasToIgnoreRecommendationNotifications()) {
|
||||
return;
|
||||
}
|
||||
recommendations = this.filterIgnoredOrNotAllowed(recommendations);
|
||||
if (recommendations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionId = recommendations[0];
|
||||
const tip = importantExeBasedRecommendations[extensionId];
|
||||
const message = localize('exeRecommended', "The '{0}' extension is recommended as you have {1} installed on your system.", tip.friendlyName!, tip.exeFriendlyName || basename(tip.windowsPath!));
|
||||
this.promptImportantExtensionInstallNotification(extensionId, message);
|
||||
}
|
||||
|
||||
private groupByInstalled(recommendationsToSuggest: string[], local: ILocalExtension[]): { installed: string[], uninstalled: string[] } {
|
||||
const installed: string[] = [], uninstalled: string[] = [];
|
||||
const installedExtensionsIds = local.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set<string>());
|
||||
recommendationsToSuggest.forEach(id => {
|
||||
if (installedExtensionsIds.has(id.toLowerCase())) {
|
||||
installed.push(id);
|
||||
} else {
|
||||
uninstalled.push(id);
|
||||
}
|
||||
});
|
||||
return { installed, uninstalled };
|
||||
}
|
||||
|
||||
private toExtensionRecommendation(tip: IExecutableBasedExtensionTip): ExtensionRecommendation {
|
||||
return {
|
||||
extensionId: tip.extensionId.toLowerCase(),
|
||||
source: 'executable',
|
||||
reason: {
|
||||
reasonId: ExtensionRecommendationReason.Executable,
|
||||
reasonText: localize('exeBasedRecommendation', "This extension is recommended because you have {0} installed.", tip.friendlyName)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user