diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts index edec6e5208975..be140e433e154 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts @@ -316,14 +316,16 @@ export class PreferencesEditor extends BaseEditor { let data = { filter, durations, - counts + counts, + requestCount: metadata && metadata['nlpResult'] && metadata['nlpResult'].requestCount }; /* __GDPR__ "defaultSettings.filter" : { "filter": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "durations" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "counts" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "counts" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "requestCount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ this.telemetryService.publicLog('defaultSettings.filter', data); diff --git a/src/vs/workbench/parts/preferences/common/preferences.ts b/src/vs/workbench/parts/preferences/common/preferences.ts index 89ec595b52e6c..75536fd71450a 100644 --- a/src/vs/workbench/parts/preferences/common/preferences.ts +++ b/src/vs/workbench/parts/preferences/common/preferences.ts @@ -110,6 +110,7 @@ export interface IFilterMetadata { timestamp: number; duration: number; scoredResults: IScoredResults; + requestCount?: number; /** The name of the server that actually served the request */ context: string; diff --git a/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts b/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts index 96bd8246bb958..11a245721119d 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts +++ b/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts @@ -132,9 +132,16 @@ interface IRemoteSearchProviderOptions { newExtensionsOnly: boolean; } +interface IBingRequestDetails { + url: string; + body?: string; + hasMoreFilters?: boolean; +} + class RemoteSearchProvider implements ISearchProvider { - // Must keep extension filter size under 8kb. 42 extension filters + core buildnum filter puts us there. - private static MAX_EXTENSION_FILTERS = 42; + // Must keep extension filter size under 8kb. 42 filters puts us there. + private static MAX_REQUEST_FILTERS = 42; + private static MAX_REQUESTS = 10; private _remoteSearchP: TPromise; @@ -144,7 +151,7 @@ class RemoteSearchProvider implements ISearchProvider { @ILogService private logService: ILogService ) { this._remoteSearchP = this.options.filter ? - this.getSettingsFromBing(this.options.filter) : + this.getSettingsForFilter(this.options.filter) : TPromise.wrap(null); } @@ -185,76 +192,100 @@ class RemoteSearchProvider implements ISearchProvider { }); } - private getSettingsFromBing(filter: string): TPromise { + private async getSettingsForFilter(filter: string): TPromise { + const allRequestDetails: IBingRequestDetails[] = []; + + // Only send MAX_REQUESTS requests in total just to keep it sane + for (let i = 0; i < RemoteSearchProvider.MAX_REQUESTS; i++) { + const details = await this.prepareRequest(filter, i); + allRequestDetails.push(details); + if (!details.hasMoreFilters) { + break; + } + } + + return TPromise.join(allRequestDetails.map(details => this.getSettingsFromBing(details))).then(allResponses => { + // Merge all IFilterMetadata + const metadata = allResponses[0]; + metadata.requestCount = 1; + + for (let response of allResponses.slice(1)) { + metadata.requestCount++; + metadata.scoredResults = { ...metadata.scoredResults, ...response.scoredResults }; + } + + return metadata; + }); + } + + private getSettingsFromBing(details: IBingRequestDetails): TPromise { + this.logService.debug(`Searching settings via ${details.url}`); + if (details.body) { + this.logService.debug(`Body: ${details.body}`); + } + + const requestType = details.body ? 'post' : 'get'; const start = Date.now(); - return this.prepareRequest(filter).then(details => { - this.logService.debug(`Searching settings via ${details.url}`); - if (details.body) { - this.logService.debug(`Body: ${details.body}`); + return this.requestService.request({ + type: requestType, + url: details.url, + data: details.body, + headers: { + 'User-Agent': 'request', + 'Content-Type': 'application/json; charset=utf-8', + 'api-key': this.options.endpoint.key + }, + timeout: 5000 + }).then(context => { + if (context.res.statusCode >= 300) { + throw new Error(`${details} returned status code: ${context.res.statusCode}`); } - const requestType = details.body ? 'post' : 'get'; - return this.requestService.request({ - type: requestType, - url: details.url, - data: details.body, - headers: { - 'User-Agent': 'request', - 'Content-Type': 'application/json; charset=utf-8', - 'api-key': this.options.endpoint.key - }, - timeout: 5000 - }).then(context => { - if (context.res.statusCode >= 300) { - throw new Error(`${details} returned status code: ${context.res.statusCode}`); - } + return asJson(context); + }).then((result: any) => { + const timestamp = Date.now(); + const duration = timestamp - start; + const remoteSettings: IRemoteSetting[] = (result.value || []) + .map(r => { + const key = JSON.parse(r.setting || r.Setting); + const packageId = r['packageid']; + const id = getSettingKey(key, packageId); + + const value = r['value']; + const defaultValue = value ? JSON.parse(value) : value; + + const packageName = r['packagename']; + let extensionName: string; + let extensionPublisher: string; + if (packageName && packageName.indexOf('##') >= 0) { + [extensionPublisher, extensionName] = packageName.split('##'); + } - return asJson(context); - }).then((result: any) => { - const timestamp = Date.now(); - const duration = timestamp - start; - const remoteSettings: IRemoteSetting[] = (result.value || []) - .map(r => { - const key = JSON.parse(r.setting || r.Setting); - const packageId = r['packageid']; - const id = getSettingKey(key, packageId); - - const value = r['value']; - const defaultValue = value ? JSON.parse(value) : value; - - const packageName = r['packagename']; - let extensionName: string; - let extensionPublisher: string; - if (packageName && packageName.indexOf('##') >= 0) { - [extensionPublisher, extensionName] = packageName.split('##'); - } - - return { - key, - id, - defaultValue, - score: r['@search.score'], - description: JSON.parse(r['details']), - packageId, - extensionName, - extensionPublisher - }; - }); - - const scoredResults = Object.create(null); - remoteSettings.forEach(s => { - scoredResults[s.id] = s; + return { + key, + id, + defaultValue, + score: r['@search.score'], + description: JSON.parse(r['details']), + packageId, + extensionName, + extensionPublisher + }; }); - return { - requestUrl: details.url, - requestBody: details.body, - duration, - timestamp, - scoredResults, - context: result['@odata.context'] - }; + const scoredResults = Object.create(null); + remoteSettings.forEach(s => { + scoredResults[s.id] = s; }); + + return { + requestUrl: details.url, + requestBody: details.body, + duration, + timestamp, + scoredResults, + context: result['@odata.context'] + }; }); } @@ -272,7 +303,7 @@ class RemoteSearchProvider implements ISearchProvider { }; } - private async prepareRequest(query: string): TPromise<{ url: string, body?: string }> { + private async prepareRequest(query: string, filterPage = 0): TPromise { query = escapeSpecialChars(query); const boost = 10; const userQuery = `(${query})^${boost}`; @@ -283,18 +314,18 @@ class RemoteSearchProvider implements ISearchProvider { const encodedQuery = encodeURIComponent(userQuery + ' || ' + query); let url = `${this.options.endpoint.urlBase}?`; - const buildNumber = this.environmentService.settingsSearchBuildId; if (this.options.endpoint.key) { url += `${API_VERSION}&${QUERY_TYPE}`; } const filters = this.options.newExtensionsOnly ? [`diminish eq 'latest'`] : - await this.getVersionFilters(buildNumber); + await this.getVersionFilters(this.environmentService.settingsSearchBuildId); const filterStr = filters - .slice(0, RemoteSearchProvider.MAX_EXTENSION_FILTERS) + .slice(filterPage * RemoteSearchProvider.MAX_REQUEST_FILTERS, (filterPage + 1) * RemoteSearchProvider.MAX_REQUEST_FILTERS) .join(' or '); + const hasMoreFilters = filters.length > (filterPage + 1) * RemoteSearchProvider.MAX_REQUEST_FILTERS; const body = JSON.stringify({ query: encodedQuery, @@ -303,7 +334,8 @@ class RemoteSearchProvider implements ISearchProvider { return { url, - body + body, + hasMoreFilters }; }