diff --git a/packages/package-stats-opt-out/.gitignore b/packages/package-stats-opt-out/.gitignore new file mode 100644 index 0000000000..677a6fc263 --- /dev/null +++ b/packages/package-stats-opt-out/.gitignore @@ -0,0 +1 @@ +.build* diff --git a/packages/package-stats-opt-out/package.js b/packages/package-stats-opt-out/package.js new file mode 100644 index 0000000000..02116f94f7 --- /dev/null +++ b/packages/package-stats-opt-out/package.js @@ -0,0 +1,9 @@ +Package.describe({ + summary: "Opt out of sending package stats", + version: '1.0.0' +}); + +Package.on_use(function (api) { + // Empty. This package's presence tells the meteor tool to stop + // sending package stats. +}); diff --git a/tools/stats.js b/tools/stats.js index 1a27fc9037..bf3b658168 100644 --- a/tools/stats.js +++ b/tools/stats.js @@ -7,6 +7,10 @@ var project = require("./project.js"); var auth = require("./auth.js"); var ServiceConnection = require("./service-connection.js"); +// The name of the package that you add to your app to opt out of +// sending stats. +var optOutPackageName = "package-stats-opt-out"; + // Return a list of packages used by this app, both directly and // indirectly. Formatted as a list of objects with 'name', 'version' // and 'direct', which is how the `recordAppPackages` method on the @@ -27,6 +31,13 @@ var packageList = function (appDir) { }; var recordPackages = function (appDir) { + // Before doing anything, look at the app's dependencies to see if the + // opt-out package is there; if present, we don't record any stats. + var packages = packageList(appDir); + if (_.contains(_.pluck(packages, "name"), optOutPackageName)) { + return; + } + // We do this inside a new fiber to avoid blocking anything on talking // to the package stats server. If we can't connect, for example, we // don't care; we'll just miss out on recording these packages. @@ -50,7 +61,7 @@ var recordPackages = function (appDir) { conn.call("recordAppPackages", project.getAppIdentifier(appDir), - packageList(appDir)); + packages); } catch (err) { // Do nothing. A failure to record package stats shouldn't be // visible to the end user and shouldn't affect whatever command diff --git a/tools/tests/report-stats.js b/tools/tests/report-stats.js index a6b5c79677..2f93f5246a 100644 --- a/tools/tests/report-stats.js +++ b/tools/tests/report-stats.js @@ -9,7 +9,7 @@ process.env.METEOR_PACKAGE_STATS_SERVER_URL = testStatsServer; // NOTE: This test will fail if your machine's time is skewed by more // than 30 minutes. This is because the `fetchAppPackageUsage` method // works by passing an hour time range. -selftest.define("report-stats", function () { +selftest.define("report-stats", ["slow"], function () { var s = new Sandbox; var run = s.run("create", "foo"); @@ -46,8 +46,23 @@ selftest.define("report-stats", function () { appPackages = stats.getPackagesForAppIdInTest(s.cwd); selftest.expectEqual(appPackages.userId, testUtils.getUserId(s)); - // TODO: - // - opt out + // Add the opt-out package, verify that no stats are recorded for the + // app. + run = s.run("add", "package-stats-opt-out"); + run.waitSecs(15); + run.expectExit(0); + bundleWithFreshIdentifier(s); + appPackages = stats.getPackagesForAppIdInTest(s.cwd); + selftest.expectEqual(appPackages, undefined); + + // Remove the opt-out package, verify that stats get sent again. + run = s.run("remove", "package-stats-opt-out"); + run.waitSecs(15); + run.expectExit(0); + bundle(s); + appPackages = stats.getPackagesForAppIdInTest(s.cwd); + selftest.expectEqual(appPackages.userId, testUtils.getUserId(s)); + selftest.expectEqual(appPackages.packages, stats.packageList(s.cwd)); }); // Bundle the app in the current working directory after deleting its @@ -55,6 +70,12 @@ selftest.define("report-stats", function () { // @param s {Sandbox} var bundleWithFreshIdentifier = function (s) { s.unlink(".meteor/identifier"); + bundle(s); +}; + +// Bundle the app in the current working directory. +// @param s {Sandbox} +var bundle = function (s) { var run = s.run("bundle", "foo.tar.gz"); run.waitSecs(30); run.expectExit(0);