From 5fa5e999f8a5126e5f565a9ab9adeaca1e93e32d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 7 Jun 2019 14:10:44 -0400 Subject: [PATCH] Plugin metrics (#7626) * feat: add enable/disable checkbox for plugin usage * feat: submit plugin data to packages.nodebb.org only submit in production mode submit once every 24 hours dont submit for plugins that have "private": true in plugin.json enabled on new installs disabled on existing installs * fix: hash not working after first send fix statusCode * fix: remove url * feat: show compatibilty * feat: add install question for submit plugin usage --- install/data/defaults.json | 3 +- .../language/en-GB/admin/extend/plugins.json | 3 ++ public/src/admin/extend/plugins.js | 10 +++++ src/controllers/admin/plugins.js | 4 ++ src/install.js | 13 ++++++ src/plugins/index.js | 3 ++ src/plugins/load.js | 13 ++++-- src/plugins/usage.js | 43 +++++++++++++++++++ src/start.js | 1 + src/upgrades/1.12.3/disable_plugin_metrics.js | 11 +++++ src/views/admin/extend/plugins.tpl | 11 +++++ .../admin/partials/download_plugin_item.tpl | 7 +++ .../admin/partials/installed_plugin_item.tpl | 11 ++++- 13 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 src/plugins/usage.js create mode 100644 src/upgrades/1.12.3/disable_plugin_metrics.js diff --git a/install/data/defaults.json b/install/data/defaults.json index be82f8a190..ab0a5f1c93 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -122,5 +122,6 @@ "eventLoopInterval": 500, "onlineCutoff": 30, "timeagoCutoff": 30, - "categoryWatchState": "watching" + "categoryWatchState": "watching", + "submitPluginUsage": 1 } \ No newline at end of file diff --git a/public/language/en-GB/admin/extend/plugins.json b/public/language/en-GB/admin/extend/plugins.json index 005d9044ae..8532b43f8b 100644 --- a/public/language/en-GB/admin/extend/plugins.json +++ b/public/language/en-GB/admin/extend/plugins.json @@ -9,6 +9,7 @@ "plugin-search": "Plugin Search", "plugin-search-placeholder": "Search for plugin...", + "submit-anonymous-usage": "Submit anonymous plugin usage data.", "reorder-plugins": "Re-order Plugins", "order-active": "Order Active Plugins", "dev-interested": "Interested in writing plugins for NodeBB?", @@ -29,6 +30,8 @@ "plugin-item.more-info": "For more information:", "plugin-item.unknown": "Unknown", "plugin-item.unknown-explanation": "The state of this plugin could not be determined, possibly due to a misconfiguration error.", + "plugin-item.compatible": "This plugin works on NodeBB %1", + "plugin-item.not-compatible": "This plugin has no compatibility data, make sure it works before installing on your production environment.", "alert.enabled": "Plugin Enabled", "alert.disabled": "Plugin Disabled", diff --git a/public/src/admin/extend/plugins.js b/public/src/admin/extend/plugins.js index 2886c3c1c7..03b49f5201 100644 --- a/public/src/admin/extend/plugins.js +++ b/public/src/admin/extend/plugins.js @@ -150,6 +150,16 @@ define('admin/extend/plugins', ['jqueryui', 'translator', 'benchpress'], functio }); }); + $('#plugin-submit-usage').on('click', function () { + socket.emit('admin.config.setMultiple', { + submitPluginUsage: $(this).prop('checked') ? '1' : '0', + }, function (err) { + if (err) { + return app.alertError(err.message); + } + }); + }); + $('#plugin-order').on('click', function () { $('#order-active-plugins-modal').modal('show'); socket.emit('admin.plugins.getActive', function (err, activePlugins) { diff --git a/src/controllers/admin/plugins.js b/src/controllers/admin/plugins.js index 5cee3aa598..e12803cf86 100644 --- a/src/controllers/admin/plugins.js +++ b/src/controllers/admin/plugins.js @@ -1,7 +1,9 @@ 'use strict'; var async = require('async'); +var nconf = require('nconf'); var plugins = require('../../plugins'); +var meta = require('../../meta'); var pluginsController = module.exports; @@ -57,6 +59,8 @@ pluginsController.get = function (req, res, next) { incompatible: payload.all.filter(function (plugin) { return !compatiblePkgNames.includes(plugin.name); }), + submitPluginUsage: meta.config.submitPluginUsage, + version: nconf.get('version'), }); }, ], next); diff --git a/src/install.js b/src/install.js index bbfecf1b05..e93b83bdd7 100644 --- a/src/install.js +++ b/src/install.js @@ -28,6 +28,11 @@ questions.main = [ description: 'Please enter a NodeBB secret', default: nconf.get('secret') || utils.generateUUID(), }, + { + name: 'submitPluginUsage', + description: 'Would you like to submit anonymous plugin usage to nbbpm?', + default: 'yes', + }, { name: 'database', description: 'Which database to use', @@ -198,6 +203,14 @@ function completeConfigSetup(config, next) { // ref: https://github.com/indexzero/nconf/issues/300 delete config.type; + var meta = require('./meta'); + meta.configs.set('submitPluginUsage', config.submitPluginUsage === 'yes' ? 1 : 0, function (err) { + next(err, config); + }); + }, + function (config, next) { + delete config.submitPluginUsage; + install.save(config, next); }, ], next); diff --git a/src/plugins/index.js b/src/plugins/index.js index 90e184dd7c..d7cd44a8ea 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -15,6 +15,7 @@ var Plugins = module.exports; require('./install')(Plugins); require('./load')(Plugins); require('./hooks')(Plugins); +require('./usage')(Plugins); Plugins.data = require('./data'); Plugins.getPluginPaths = Plugins.data.getPluginPaths; @@ -33,6 +34,7 @@ Plugins.libraryPaths = []; Plugins.versionWarning = []; Plugins.soundpacks = []; Plugins.languageData = {}; +Plugins.loadedPlugins = []; Plugins.initialized = false; @@ -105,6 +107,7 @@ Plugins.reload = function (callback) { Plugins.clientScripts.length = 0; Plugins.acpScripts.length = 0; Plugins.libraryPaths.length = 0; + Plugins.loadedPlugins.length = 0; async.waterfall([ Plugins.getPluginPaths, diff --git a/src/plugins/load.js b/src/plugins/load.js index 3d39b24d44..92436d86f7 100644 --- a/src/plugins/load.js +++ b/src/plugins/load.js @@ -150,8 +150,16 @@ module.exports = function (Plugins) { }, ], function (err) { if (err) { + winston.error(err.stack); winston.verbose('[plugins] Could not load plugin : ' + pluginData.id); - return callback(err); + return callback(); + } + + if (!pluginData.private) { + Plugins.loadedPlugins.push({ + id: pluginData.id, + version: pluginData.version, + }); } winston.verbose('[plugins] Loaded plugin: ' + pluginData.id); @@ -196,9 +204,8 @@ module.exports = function (Plugins) { callback(); } } catch (err) { - winston.error(err.stack); winston.warn('[plugins] Unable to parse library for: ' + pluginData.id); - callback(); + callback(err); } } }; diff --git a/src/plugins/usage.js b/src/plugins/usage.js new file mode 100644 index 0000000000..1c1d86478f --- /dev/null +++ b/src/plugins/usage.js @@ -0,0 +1,43 @@ +'use strict'; + +const nconf = require('nconf'); +const request = require('request'); +const winston = require('winston'); +const crypto = require('crypto'); +const cronJob = require('cron').CronJob; + +const pkg = require('../../package.json'); + +const meta = require('../meta'); + +module.exports = function (Plugins) { + Plugins.startJobs = function () { + new cronJob('0 0 0 * * *', function () { + Plugins.submitUsageData(); + }, null, true); + }; + + Plugins.submitUsageData = function () { + if (!meta.config.submitPluginUsage || !Plugins.loadedPlugins.length || global.env !== 'production') { + return; + } + + const hash = crypto.createHash('sha256'); + hash.update(nconf.get('url')); + request.post((nconf.get('registry') || 'https://packages.nodebb.org') + '/api/v1/plugin/usage', { + form: { + id: hash.digest('hex'), + version: pkg.version, + plugins: Plugins.loadedPlugins, + }, + timeout: 5000, + }, function (err, res, body) { + if (err) { + return winston.error(err); + } + if (res.statusCode !== 200) { + winston.error('[plugins.submitUsageData] received ' + res.statusCode + ' ' + body); + } + }); + }; +}; diff --git a/src/start.js b/src/start.js index b2b4ceebc9..ef1d624d65 100644 --- a/src/start.js +++ b/src/start.js @@ -50,6 +50,7 @@ start.start = function () { if (nconf.get('runJobs')) { require('./notifications').startJobs(); require('./user').startJobs(); + require('./plugins').startJobs(); } webserver.listen(next); diff --git a/src/upgrades/1.12.3/disable_plugin_metrics.js b/src/upgrades/1.12.3/disable_plugin_metrics.js new file mode 100644 index 0000000000..666949d8ef --- /dev/null +++ b/src/upgrades/1.12.3/disable_plugin_metrics.js @@ -0,0 +1,11 @@ +'use strict'; + +const db = require('../../database'); + +module.exports = { + name: 'Disable plugin metrics for existing installs', + timestamp: Date.UTC(2019, 4, 21), + method: async function (callback) { + db.setObjectField('config', 'submitPluginUsage', 0, callback); + }, +}; diff --git a/src/views/admin/extend/plugins.tpl b/src/views/admin/extend/plugins.tpl index 795c404656..d1df022dd8 100644 --- a/src/views/admin/extend/plugins.tpl +++ b/src/views/admin/extend/plugins.tpl @@ -28,6 +28,17 @@ +
+
+
+ +
+
+
+
[[admin/extend/plugins:reorder-plugins]]
diff --git a/src/views/admin/partials/download_plugin_item.tpl b/src/views/admin/partials/download_plugin_item.tpl index c5bc9be4dd..0709f45ea7 100644 --- a/src/views/admin/partials/download_plugin_item.tpl +++ b/src/views/admin/partials/download_plugin_item.tpl @@ -11,6 +11,13 @@ [[admin/extend/plugins:plugin-item.latest]] {download.latest} +

+ + [[admin/extend/plugins:plugin-item.compatible, {version}]] + + [[admin/extend/plugins:plugin-item.not-compatible]] + +

[[admin/extend/plugins:plugin-item.more-info]] {download.url}

diff --git a/src/views/admin/partials/installed_plugin_item.tpl b/src/views/admin/partials/installed_plugin_item.tpl index 0a7524c815..dec83e3ea7 100644 --- a/src/views/admin/partials/installed_plugin_item.tpl +++ b/src/views/admin/partials/installed_plugin_item.tpl @@ -24,9 +24,18 @@ [[admin/extend/plugins:plugin-item.installed]] {installed.version} | [[admin/extend/plugins:plugin-item.latest]] {installed.latest} + - + +

+ + [[admin/extend/plugins:plugin-item.compatible, {version}]] + + [[admin/extend/plugins:plugin-item.not-compatible]] + +

+

[[admin/extend/plugins:plugin-item.more-info]] {installed.url}