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
v1.18.x
Barış Soner Uşaklı 6 years ago committed by GitHub
parent 3f4f8aface
commit 5fa5e999f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -122,5 +122,6 @@
"eventLoopInterval": 500, "eventLoopInterval": 500,
"onlineCutoff": 30, "onlineCutoff": 30,
"timeagoCutoff": 30, "timeagoCutoff": 30,
"categoryWatchState": "watching" "categoryWatchState": "watching",
"submitPluginUsage": 1
} }

@ -9,6 +9,7 @@
"plugin-search": "Plugin Search", "plugin-search": "Plugin Search",
"plugin-search-placeholder": "Search for plugin...", "plugin-search-placeholder": "Search for plugin...",
"submit-anonymous-usage": "Submit anonymous plugin usage data.",
"reorder-plugins": "Re-order Plugins", "reorder-plugins": "Re-order Plugins",
"order-active": "Order Active Plugins", "order-active": "Order Active Plugins",
"dev-interested": "Interested in writing plugins for NodeBB?", "dev-interested": "Interested in writing plugins for NodeBB?",
@ -29,6 +30,8 @@
"plugin-item.more-info": "For more information:", "plugin-item.more-info": "For more information:",
"plugin-item.unknown": "Unknown", "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.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.enabled": "Plugin Enabled",
"alert.disabled": "Plugin Disabled", "alert.disabled": "Plugin Disabled",

@ -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 () { $('#plugin-order').on('click', function () {
$('#order-active-plugins-modal').modal('show'); $('#order-active-plugins-modal').modal('show');
socket.emit('admin.plugins.getActive', function (err, activePlugins) { socket.emit('admin.plugins.getActive', function (err, activePlugins) {

@ -1,7 +1,9 @@
'use strict'; 'use strict';
var async = require('async'); var async = require('async');
var nconf = require('nconf');
var plugins = require('../../plugins'); var plugins = require('../../plugins');
var meta = require('../../meta');
var pluginsController = module.exports; var pluginsController = module.exports;
@ -57,6 +59,8 @@ pluginsController.get = function (req, res, next) {
incompatible: payload.all.filter(function (plugin) { incompatible: payload.all.filter(function (plugin) {
return !compatiblePkgNames.includes(plugin.name); return !compatiblePkgNames.includes(plugin.name);
}), }),
submitPluginUsage: meta.config.submitPluginUsage,
version: nconf.get('version'),
}); });
}, },
], next); ], next);

@ -28,6 +28,11 @@ questions.main = [
description: 'Please enter a NodeBB secret', description: 'Please enter a NodeBB secret',
default: nconf.get('secret') || utils.generateUUID(), default: nconf.get('secret') || utils.generateUUID(),
}, },
{
name: 'submitPluginUsage',
description: 'Would you like to submit anonymous plugin usage to nbbpm?',
default: 'yes',
},
{ {
name: 'database', name: 'database',
description: 'Which database to use', description: 'Which database to use',
@ -198,6 +203,14 @@ function completeConfigSetup(config, next) {
// ref: https://github.com/indexzero/nconf/issues/300 // ref: https://github.com/indexzero/nconf/issues/300
delete config.type; 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); install.save(config, next);
}, },
], next); ], next);

@ -15,6 +15,7 @@ var Plugins = module.exports;
require('./install')(Plugins); require('./install')(Plugins);
require('./load')(Plugins); require('./load')(Plugins);
require('./hooks')(Plugins); require('./hooks')(Plugins);
require('./usage')(Plugins);
Plugins.data = require('./data'); Plugins.data = require('./data');
Plugins.getPluginPaths = Plugins.data.getPluginPaths; Plugins.getPluginPaths = Plugins.data.getPluginPaths;
@ -33,6 +34,7 @@ Plugins.libraryPaths = [];
Plugins.versionWarning = []; Plugins.versionWarning = [];
Plugins.soundpacks = []; Plugins.soundpacks = [];
Plugins.languageData = {}; Plugins.languageData = {};
Plugins.loadedPlugins = [];
Plugins.initialized = false; Plugins.initialized = false;
@ -105,6 +107,7 @@ Plugins.reload = function (callback) {
Plugins.clientScripts.length = 0; Plugins.clientScripts.length = 0;
Plugins.acpScripts.length = 0; Plugins.acpScripts.length = 0;
Plugins.libraryPaths.length = 0; Plugins.libraryPaths.length = 0;
Plugins.loadedPlugins.length = 0;
async.waterfall([ async.waterfall([
Plugins.getPluginPaths, Plugins.getPluginPaths,

@ -150,8 +150,16 @@ module.exports = function (Plugins) {
}, },
], function (err) { ], function (err) {
if (err) { if (err) {
winston.error(err.stack);
winston.verbose('[plugins] Could not load plugin : ' + pluginData.id); 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); winston.verbose('[plugins] Loaded plugin: ' + pluginData.id);
@ -196,9 +204,8 @@ module.exports = function (Plugins) {
callback(); callback();
} }
} catch (err) { } catch (err) {
winston.error(err.stack);
winston.warn('[plugins] Unable to parse library for: ' + pluginData.id); winston.warn('[plugins] Unable to parse library for: ' + pluginData.id);
callback(); callback(err);
} }
} }
}; };

@ -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);
}
});
};
};

@ -50,6 +50,7 @@ start.start = function () {
if (nconf.get('runJobs')) { if (nconf.get('runJobs')) {
require('./notifications').startJobs(); require('./notifications').startJobs();
require('./user').startJobs(); require('./user').startJobs();
require('./plugins').startJobs();
} }
webserver.listen(next); webserver.listen(next);

@ -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);
},
};

@ -28,6 +28,17 @@
</div> </div>
</div> </div>
<div class="panel panel-default">
<div class="panel-body">
<div class="checkbox">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input id="plugin-submit-usage" class="mdl-switch__input" type="checkbox" data-field="submitPluginUsage" <!-- IF submitPluginUsage -->checked<!-- ENDIF submitPluginUsage -->/>
<span class="mdl-switch__label">[[admin/extend/plugins:submit-anonymous-usage]]</span>
</label>
</div>
</div>
</div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading">[[admin/extend/plugins:reorder-plugins]]</div> <div class="panel-heading">[[admin/extend/plugins:reorder-plugins]]</div>
<div class="panel-body"> <div class="panel-body">

@ -11,6 +11,13 @@
<!-- ENDIF download.description --> <!-- ENDIF download.description -->
<small>[[admin/extend/plugins:plugin-item.latest]] <strong class="latestVersion">{download.latest}</strong></small> <small>[[admin/extend/plugins:plugin-item.latest]] <strong class="latestVersion">{download.latest}</strong></small>
<p>
<!-- IF installed.isCompatible -->
<i class="fa fa-check text-success"></i> [[admin/extend/plugins:plugin-item.compatible, {version}]]
<!-- ELSE -->
<i class="fa fa-question text-warning"></i> [[admin/extend/plugins:plugin-item.not-compatible]]
<!-- ENDIF -->
</p>
<!-- IF download.url --> <!-- IF download.url -->
<p>[[admin/extend/plugins:plugin-item.more-info]] <a target="_blank" href="{download.url}">{download.url}</a></p> <p>[[admin/extend/plugins:plugin-item.more-info]] <a target="_blank" href="{download.url}">{download.url}</a></p>

@ -24,9 +24,18 @@
<!-- ENDIF installed.description --> <!-- ENDIF installed.description -->
<!-- IF installed.outdated --><i class="fa fa-exclamation-triangle text-danger"></i> <!-- ENDIF installed.outdated --> <!-- IF installed.outdated --><i class="fa fa-exclamation-triangle text-danger"></i> <!-- ENDIF installed.outdated -->
<small>[[admin/extend/plugins:plugin-item.installed]] <strong class="currentVersion">{installed.version}</strong> | [[admin/extend/plugins:plugin-item.latest]] <strong class="latestVersion">{installed.latest}</strong></small> <small>[[admin/extend/plugins:plugin-item.installed]] <strong class="currentVersion">{installed.version}</strong> | [[admin/extend/plugins:plugin-item.latest]] <strong class="latestVersion">{installed.latest}</strong></small>
<!-- IF installed.outdated --> <!-- IF installed.outdated -->
<button data-action="upgrade" class="btn btn-success btn-xs"><i class="fa fa-download"></i> [[admin/extend/plugins:plugin-item.upgrade]]</button> <button data-action="upgrade" class="btn btn-success btn-xs"><i class="fa fa-download"></i> [[admin/extend/plugins:plugin-item.upgrade]]</button>
<p>
<!-- IF installed.isCompatible -->
<i class="fa fa-check text-success"></i> [[admin/extend/plugins:plugin-item.compatible, {version}]]
<!-- ELSE -->
<i class="fa fa-question text-warning"></i> [[admin/extend/plugins:plugin-item.not-compatible]]
<!-- ENDIF -->
</p>
<!-- ENDIF installed.outdated --> <!-- ENDIF installed.outdated -->
<!-- IF installed.url --> <!-- IF installed.url -->
<p>[[admin/extend/plugins:plugin-item.more-info]] <a target="_blank" href="{installed.url}">{installed.url}</a></p> <p>[[admin/extend/plugins:plugin-item.more-info]] <a target="_blank" href="{installed.url}">{installed.url}</a></p>
<!-- ENDIF installed.url --> <!-- ENDIF installed.url -->

Loading…
Cancel
Save