Merge pull request #185239 from microsoft/isidor/sound-antlion

remove dependency on experiment service for search
This commit is contained in:
Isidor Nikolic
2023-06-19 11:45:04 +02:00
committed by GitHub
11 changed files with 46 additions and 189 deletions
@@ -692,7 +692,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
return manifest;
} catch (err) {
this.logService.trace('ExtensionManagementService.refreshControlCache - failed to get extension control manifest');
return { malicious: [], deprecated: {} };
return { malicious: [], deprecated: {}, search: [] };
}
}
@@ -15,7 +15,7 @@ import { URI } from 'vs/base/common/uri';
import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { getFallbackTargetPlarforms, getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getFallbackTargetPlarforms, getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo, ISearchPrefferedResults } from 'vs/platform/extensionManagement/common/extensionManagement';
import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions';
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator';
@@ -574,6 +574,7 @@ interface IRawExtensionsControlManifest {
settings?: string[];
additionalInfo?: string;
}>;
search?: ISearchPrefferedResults[];
}
abstract class AbstractExtensionGalleryService implements IExtensionGalleryService {
@@ -1206,7 +1207,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
}
if (!this.extensionsControlUrl) {
return { malicious: [], deprecated: {} };
return { malicious: [], deprecated: {}, search: [] };
}
const context = await this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }, CancellationToken.None);
@@ -1217,6 +1218,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
const result = await asJson<IRawExtensionsControlManifest>(context);
const malicious: IExtensionIdentifier[] = [];
const deprecated: IStringDictionary<IDeprecationInfo> = {};
const search: ISearchPrefferedResults[] = [];
if (result) {
for (const id of result.malicious) {
malicious.push({ id });
@@ -1243,9 +1245,14 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
}
}
}
if (result.search) {
for (const s of result.search) {
search.push(s);
}
}
}
return { malicious, deprecated };
return { malicious, deprecated, search };
}
}
@@ -311,9 +311,15 @@ export interface IDeprecationInfo {
readonly additionalInfo?: string;
}
export interface ISearchPrefferedResults {
readonly query?: string;
readonly preferredResults?: string[];
}
export interface IExtensionsControlManifest {
readonly malicious: IExtensionIdentifier[];
readonly deprecated: IStringDictionary<IDeprecationInfo>;
readonly search: ISearchPrefferedResults[];
}
export const enum InstallOperation {
@@ -75,7 +75,6 @@ export interface IExperimentService {
readonly _serviceBrand: undefined;
getExperimentById(id: string): Promise<IExperiment>;
getExperimentsByType(type: ExperimentActionType): Promise<IExperiment[]>;
getCuratedExtensionsList(curatedExtensionsKey: string): Promise<string[]>;
markAsCompleted(experimentId: string): void;
onExperimentEnabled: Event<IExperiment>;
@@ -207,20 +206,6 @@ export class ExperimentService extends Disposable implements IExperimentService
});
}
public getCuratedExtensionsList(curatedExtensionsKey: string): Promise<string[]> {
return this._loadExperimentsPromise.then(() => {
for (const experiment of this._experiments) {
if (experiment.enabled
&& experiment.state === ExperimentState.Run
&& this._curatedMapping[experiment.id]
&& this._curatedMapping[experiment.id].curatedExtensionsKey === curatedExtensionsKey) {
return this._curatedMapping[experiment.id].curatedExtensionsList;
}
}
return [];
});
}
public markAsCompleted(experimentId: string): void {
const storageKey = 'experiments.' + experimentId;
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.APPLICATION), {});
@@ -766,87 +766,6 @@ suite('Experiment Service', () => {
});
});
test('Curated list should be available if experiment is enabled.', () => {
const promptText = 'Hello there! Can you see this?';
const curatedExtensionsKey = 'AzureDeploy';
const curatedExtensionsList = ['uninstalled-extention-id1', 'uninstalled-extention-id2'];
experimentData = {
experiments: [
{
id: 'experiment1',
enabled: true,
action: {
type: 'Prompt',
properties: {
promptText,
commands: [
{
text: 'Search Marketplace',
dontShowAgain: true,
curatedExtensionsKey,
curatedExtensionsList
},
{
text: 'No'
}
]
}
}
}
]
};
testObject = instantiationService.createInstance(TestExperimentService);
return testObject.getExperimentById('experiment1').then(result => {
assert.strictEqual(result.enabled, true);
assert.strictEqual(result.state, ExperimentState.Run);
return testObject.getCuratedExtensionsList(curatedExtensionsKey).then(curatedList => {
assert.strictEqual(curatedList, curatedExtensionsList);
});
});
});
test('Curated list shouldnt be available if experiment is disabled.', () => {
const promptText = 'Hello there! Can you see this?';
const curatedExtensionsKey = 'AzureDeploy';
const curatedExtensionsList = ['uninstalled-extention-id1', 'uninstalled-extention-id2'];
experimentData = {
experiments: [
{
id: 'experiment1',
enabled: false,
action: {
type: 'Prompt',
properties: {
promptText,
commands: [
{
text: 'Search Marketplace',
dontShowAgain: true,
curatedExtensionsKey,
curatedExtensionsList
},
{
text: 'No'
}
]
}
}
}
]
};
testObject = instantiationService.createInstance(TestExperimentService);
return testObject.getExperimentById('experiment1').then(result => {
assert.strictEqual(result.enabled, false);
assert.strictEqual(result.action?.type, 'Prompt');
assert.strictEqual(result.state, ExperimentState.NoRun);
return testObject.getCuratedExtensionsList(curatedExtensionsKey).then(curatedList => {
assert.strictEqual(curatedList.length, 0);
});
});
});
test('Maps action2 to action.', () => {
experimentData = {
experiments: [
@@ -32,7 +32,6 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { coalesce, distinct, flatten } from 'vs/base/common/arrays';
import { IExperimentService, IExperiment, ExperimentActionType } from 'vs/workbench/contrib/experiments/common/experimentService';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list';
import { CancellationToken } from 'vs/base/common/cancellation';
@@ -134,7 +133,6 @@ export class ExtensionsListView extends ViewPane {
@ITelemetryService telemetryService: ITelemetryService,
@IConfigurationService configurationService: IConfigurationService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IExperimentService private readonly experimentService: IExperimentService,
@IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService,
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
@IWorkbenchExtensionManagementService protected readonly extensionManagementService: IWorkbenchExtensionManagementService,
@@ -734,10 +732,6 @@ export class ExtensionsListView extends ViewPane {
return this.queryRecommendations(query, options, token);
}
if (/\bcurated:([^\s]+)\b/.test(query.value)) {
return this.getCuratedModel(query, options, token);
}
const text = query.value;
if (/\bext:([^\s]+)\b/g.test(text)) {
@@ -751,12 +745,14 @@ export class ExtensionsListView extends ViewPane {
options.text = text.substring(0, 350);
options.source = 'searchText';
if (!hasUserDefinedSortOrder) {
const searchExperiments = await this.getSearchExperiments();
for (const experiment of searchExperiments) {
if (experiment.action && text.toLowerCase() === experiment.action.properties['searchText'] && Array.isArray(experiment.action.properties['preferredResults'])) {
preferredResults = experiment.action.properties['preferredResults'];
options.source += `-experiment-${experiment.id}`;
break;
const manifest = await this.extensionManagementService.getExtensionsControlManifest();
const search = manifest.search;
if (Array.isArray(search)) {
for (const s of search) {
if (s.query && s.query.toLowerCase() === text.toLowerCase() && Array.isArray(s.preferredResults)) {
preferredResults = s.preferredResults;
break;
}
}
}
}
@@ -783,19 +779,6 @@ export class ExtensionsListView extends ViewPane {
}
resetSearchExperiments() { ExtensionsListView.searchExperiments = undefined; }
private static searchExperiments: Promise<IExperiment[]> | undefined;
private getSearchExperiments(): Promise<IExperiment[]> {
if (!ExtensionsListView.searchExperiments) {
ExtensionsListView.searchExperiments = this.experimentService.getExperimentsByType(ExperimentActionType.ExtensionSearchResults)
.then(null, e => {
this.logService.error(e);
return [];
});
}
return ExtensionsListView.searchExperiments;
}
private sortExtensions(extensions: IExtension[], options: IQueryOptions): IExtension[] {
switch (options.sortBy) {
case GallerySortBy.InstallCount:
@@ -821,20 +804,6 @@ export class ExtensionsListView extends ViewPane {
return extensions;
}
private async getCuratedModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
const value = query.value.replace(/curated:/g, '').trim();
let ids = await this.experimentService.getCuratedExtensionsList(value);
if (Array.isArray(ids) && ids.length) {
ids = ids.map(id => id.toLowerCase());
const extensions = await this.extensionsWorkbenchService.getExtensions(ids.map(id => ({ id })), { source: `curated:${value}` }, token);
// Sorts the firstPage of the pager in the same order as given array of extension ids
extensions.sort((a, b) =>
ids.indexOf(a.identifier.id.toLowerCase()) < ids.indexOf(b.identifier.id.toLowerCase()) ? -1 : 1);
return this.getPagedModel(extensions);
}
return new PagedModel([]);
}
private isRecommendationsQuery(query: Query): boolean {
return ExtensionsListView.isWorkspaceRecommendedExtensionsQuery(query.value)
|| ExtensionsListView.isKeymapsRecommendedExtensionsQuery(query.value)
@@ -222,7 +222,7 @@ suite('ExtensionRecommendationsService Test', () => {
onDidChangeProfile: Event.None,
async getInstalled() { return []; },
async canInstall() { return true; },
async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; },
async getExtensionsControlManifest() { return { malicious: [], deprecated: {}, search: [] }; },
async getTargetPlatform() { return getTargetPlatform(platform, arch); }
});
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
@@ -99,7 +99,7 @@ function setupTest() {
onDidChangeProfile: Event.None,
onDidUpdateExtensionMetadata: Event.None,
async getInstalled() { return []; },
async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; },
async getExtensionsControlManifest() { return { malicious: [], deprecated: {}, search: [] }; },
async updateMetadata(local: ILocalExtension, metadata: Partial<Metadata>) {
local.identifier.uuid = metadata.id;
local.publisherDisplayName = metadata.publisherDisplayName!;
@@ -2641,7 +2641,7 @@ function createExtensionManagementService(installed: ILocalExtension[] = []): IP
return local;
},
async getTargetPlatform() { return getTargetPlatform(platform, arch); },
async getExtensionsControlManifest() { return <IExtensionsControlManifest>{ malicious: [], deprecated: {} }; },
async getExtensionsControlManifest() { return <IExtensionsControlManifest>{ malicious: [], deprecated: {}, search: [] }; },
};
}
@@ -13,7 +13,7 @@ import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions,
DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, getTargetPlatform, IExtensionInfo, UninstallExtensionEvent, SortBy
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IProfileAwareExtensionManagementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IExtensionRecommendationsService, ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test';
@@ -33,7 +33,7 @@ import { NativeURLService } from 'vs/platform/url/common/urlService';
import { URI } from 'vs/base/common/uri';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { SinonStub } from 'sinon';
import { IExperimentService, ExperimentState, ExperimentActionType, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
import { IExperimentService, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentService';
import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions';
@@ -103,7 +103,7 @@ suite('ExtensionsViews Tests', () => {
onDidUpdateExtensionMetadata: Event.None,
async getInstalled() { return []; },
async canInstall() { return true; },
async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; },
async getExtensionsControlManifest() { return { malicious: [], deprecated: {}, search: [] }; },
async getTargetPlatform() { return getTargetPlatform(platform, arch); },
async updateMetadata(local) { return local; }
});
@@ -462,29 +462,6 @@ suite('ExtensionsViews Tests', () => {
});
});
test('Test curated list experiment', () => {
const curatedList = [
workspaceRecommendationA,
fileBasedRecommendationA
];
const experimentTarget = <SinonStub>instantiationService.stubPromise(IExperimentService, 'getCuratedExtensionsList', curatedList.map(e => e.identifier.id));
const queryTarget = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'getExtensions', curatedList);
return testableView.show('curated:mykey').then(result => {
const curatedKey: string = experimentTarget.args[0][0];
const extensionInfos: IExtensionInfo[] = queryTarget.args[0][0];
assert.ok(experimentTarget.calledOnce);
assert.strictEqual(extensionInfos.length, curatedList.length);
assert.strictEqual(result.length, curatedList.length);
for (let i = 0; i < curatedList.length; i++) {
assert.strictEqual(extensionInfos[i].id, curatedList[i].identifier.id);
assert.strictEqual(result.get(i).identifier.id, curatedList[i].identifier.id);
}
assert.strictEqual(curatedKey, 'mykey');
});
});
test('Test search', () => {
const searchText = 'search-me';
const results = [
@@ -522,31 +499,25 @@ suite('ExtensionsViews Tests', () => {
];
const queryTarget = <SinonStub>instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...actual));
const experimentTarget = <SinonStub>instantiationService.stubPromise(IExperimentService, 'getExperimentsByType', [{
id: 'someId',
enabled: true,
state: ExperimentState.Run,
action: {
type: ExperimentActionType.ExtensionSearchResults,
properties: {
searchText: 'search-me',
preferredResults: [
workspaceRecommendationA.identifier.id,
'something-that-wasnt-in-first-page',
workspaceRecommendationB.identifier.id
]
}
}
}]);
const experimentTarget = <SinonStub>instantiationService.stubPromise(IWorkbenchExtensionManagementService, 'getExtensionsControlManifest', {
malicious: [], deprecated: {},
search: [{
query: 'search-me',
preferredResults: [
workspaceRecommendationA.identifier.id,
'something-that-wasnt-in-first-page',
workspaceRecommendationB.identifier.id
]
}]
});
testableView.resetSearchExperiments();
testableView.dispose();
testableView = instantiationService.createInstance(ExtensionsListView, {}, { id: '', title: '' });
return testableView.show('search-me').then(result => {
const options: IQueryOptions = queryTarget.args[0][0];
assert.ok(experimentTarget.calledOnce);
assert.ok(experimentTarget.calledTwice);
assert.ok(queryTarget.calledOnce);
assert.strictEqual(options.text, searchText);
assert.strictEqual(result.length, expected.length);
@@ -96,7 +96,7 @@ suite('ExtensionsWorkbenchServiceTest', () => {
onDidChangeProfile: Event.None,
onDidUpdateExtensionMetadata: Event.None,
async getInstalled() { return []; },
async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; },
async getExtensionsControlManifest() { return { malicious: [], deprecated: {}, search: [] }; },
async updateMetadata(local: ILocalExtension, metadata: Partial<Metadata>) {
local.identifier.uuid = metadata.id;
local.publisherDisplayName = metadata.publisherDisplayName!;
@@ -1498,7 +1498,7 @@ suite('ExtensionsWorkbenchServiceTest', () => {
return local;
},
getTargetPlatform: async () => getTargetPlatform(platform, arch),
async getExtensionsControlManifest() { return <IExtensionsControlManifest>{ malicious: [], deprecated: {} }; },
async getExtensionsControlManifest() { return <IExtensionsControlManifest>{ malicious: [], deprecated: {}, search: [] }; },
};
}
});
@@ -448,7 +448,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
if (this.extensionManagementServerService.webExtensionManagementServer) {
return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.getExtensionsControlManifest();
}
return Promise.resolve({ malicious: [], deprecated: {} });
return Promise.resolve({ malicious: [], deprecated: {}, search: [] });
}
private getServer(extension: ILocalExtension): IExtensionManagementServer | null {