diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index eebeee7101a56..3e241e508088e 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -195,12 +195,17 @@ export interface IQueryOptions { sortOrder?: SortOrder; } +export enum StatisticType { + Uninstall = 'uninstall' +} + export interface IExtensionGalleryService { _serviceBrand: any; isEnabled(): boolean; getRequestHeaders(): TPromise<{ [key: string]: string; }>; query(options?: IQueryOptions): TPromise>; download(extension: IGalleryExtension): TPromise; + reportStatistic(publisher: string, name: string, version: string, type: StatisticType): TPromise; getReadme(extension: IGalleryExtension): TPromise; getManifest(extension: IGalleryExtension): TPromise; getChangelog(extension: IGalleryMetadata): TPromise; diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index a70e63cbb760e..cd396127d1ea2 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -10,7 +10,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as uuid from 'vs/base/common/uuid'; import { distinct } from 'vs/base/common/arrays'; import { getErrorMessage } from 'vs/base/common/errors'; -import { IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId, getGalleryExtensionTelemetryData, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { assign, getOrDefault } from 'vs/base/common/objects'; import { IRequestService } from 'vs/platform/request/node/request'; @@ -385,6 +385,27 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return { galleryExtensions, total }; } + async reportStatistic(publisher: string, name: string, version: string, type: StatisticType): TPromise { + if (!this.isEnabled()) { + return; + } + + try { + const headers = { + ...await this.commonHTTPHeaders, + Accept: '*/*;api-version=4.0-preview.1' + }; + + await this.requestService.request({ + type: 'POST', + url: this.api(`/publishers/${publisher}/extensions/${name}/${version}/stats?statType=${type}`), + headers + }); + } catch (err) { + // noop + } + } + download(extension: IGalleryExtension): TPromise { return this.loadCompatibleVersion(extension).then(extension => { const zipPath = path.join(tmpdir(), uuid.generateUuid()); diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 19142629db98a..e019353ebcec5 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -13,11 +13,12 @@ import { assign } from 'vs/base/common/objects'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { flatten, distinct } from 'vs/base/common/arrays'; import { extract, buffer } from 'vs/base/node/zip'; -import { Promise, TPromise } from 'vs/base/common/winjs.base'; +import { TPromise } from 'vs/base/common/winjs.base'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IExtensionManifest, IGalleryMetadata, - InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, LocalExtensionType + InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, LocalExtensionType, + StatisticType } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getLocalExtensionIdFromGallery, getLocalExtensionIdFromManifest, getGalleryExtensionIdFromLocal, getIdAndVersionFromLocalExtensionId, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localizeManifest } from '../common/extensionNls'; @@ -223,9 +224,9 @@ export class ExtensionManagementService implements IExtensionManagementService { } private rollback(localExtension: ILocalExtension, dependecies: IGalleryExtension[]): TPromise { - return this.doUninstall(localExtension.id) + return this.doUninstall(localExtension) .then(() => this.filterOutUninstalled(dependecies)) - .then(installed => TPromise.join(installed.map((i) => this.doUninstall(i.id)))) + .then(installed => TPromise.join(installed.map((i) => this.doUninstall(i)))) .then(() => null); } @@ -304,11 +305,11 @@ export class ExtensionManagementService implements IExtensionManagementService { } private checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[], force: boolean): TPromise { - return this.preUninstallExtension(extension.id) + return this.preUninstallExtension(extension) .then(() => this.hasDependencies(extension, installed) ? this.promptForDependenciesAndUninstall(extension, installed, force) : this.promptAndUninstall(extension, installed, force)) - .then(() => this.postUninstallExtension(extension.id), + .then(() => this.postUninstallExtension(extension), error => { - this.postUninstallExtension(extension.id, error); + this.postUninstallExtension(extension, error); return TPromise.wrapError(error); }); } @@ -370,7 +371,7 @@ export class ExtensionManagementService implements IExtensionManagementService { if (dependents.length) { return TPromise.wrapError(new Error(this.getDependentsErrorMessage(extension, dependents))); } - return TPromise.join([this.uninstallExtension(extension.id), ...dependenciesToUninstall.map(d => this.doUninstall(d.id))]).then(() => null); + return TPromise.join([this.uninstallExtension(extension.id), ...dependenciesToUninstall.map(d => this.doUninstall(d))]).then(() => null); } private getDependentsErrorMessage(extension: ILocalExtension, dependents: ILocalExtension[]): string { @@ -419,21 +420,21 @@ export class ExtensionManagementService implements IExtensionManagementService { return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.indexOf(getGalleryExtensionIdFromLocal(extension)) !== -1); } - private doUninstall(id: string): TPromise { - return this.preUninstallExtension(id) - .then(() => this.uninstallExtension(id)) - .then(() => this.postUninstallExtension(id), + private doUninstall(extension: ILocalExtension): TPromise { + return this.preUninstallExtension(extension) + .then(() => this.uninstallExtension(extension.id)) + .then(() => this.postUninstallExtension(extension), error => { - this.postUninstallExtension(id, error); + this.postUninstallExtension(extension, error); return TPromise.wrapError(error); }); } - private preUninstallExtension(id: string): TPromise { - const extensionPath = path.join(this.extensionsPath, id); + private preUninstallExtension(extension: ILocalExtension): TPromise { + const extensionPath = path.join(this.extensionsPath, extension.id); return pfs.exists(extensionPath) .then(exists => exists ? null : TPromise.wrapError(new Error(nls.localize('notExists', "Could not find extension")))) - .then(() => this._onUninstallExtension.fire(id)); + .then(() => this._onUninstallExtension.fire(extension.id)); } private uninstallExtension(id: string): TPromise { @@ -443,8 +444,12 @@ export class ExtensionManagementService implements IExtensionManagementService { .then(() => this.unsetObsolete(id)); } - private postUninstallExtension(id: string, error?: any): TPromise { - return this._onDidUninstallExtension.fire({ id, error }); + private async postUninstallExtension(extension: ILocalExtension, error?: any): TPromise { + if (!error) { + await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall); + } + + this._onDidUninstallExtension.fire({ id: extension.id, error }); } getInstalled(type: LocalExtensionType = null): TPromise { @@ -476,7 +481,7 @@ export class ExtensionManagementService implements IExtensionManagementService { const limiter = new Limiter(10); return this.scanExtensionFolders(root) - .then(extensionIds => Promise.join(extensionIds.map(id => { + .then(extensionIds => TPromise.join(extensionIds.map(id => { const extensionPath = path.join(root, id); const each = () => pfs.readdir(extensionPath).then(children => {