Files
shields/lib/analytics.js
Paul Melnikow dc44ba7725 Clean up request-handler, github-auth, and analytics; upgrade to Mocha 4 (#1142)
- Add tests to request-handler to prepare for some tweaks to caching for #820
- Clean up code in request-handler: renames, DRY, arrows, imports
- Allow for clean shutdown of `setInterval` code. This requires the ability to cancel autosaving.
- Upgrade to Mocha 4, and clean up so the process exits on its own (see mochajs/mocha#3044)
- Better encapsulate analytics
2017-10-17 22:01:46 -04:00

144 lines
4.1 KiB
JavaScript

'use strict';
const fs = require('fs');
// We can either use a process-wide object regularly saved to a JSON file,
// or a Redis equivalent (for multi-process / when the filesystem is unreliable.
let redis;
let useRedis = false;
if (process.env.REDISTOGO_URL) {
const redisToGo = require('url').parse(process.env.REDISTOGO_URL);
redis = require('redis').createClient(redisToGo.port, redisToGo.hostname);
redis.auth(redisToGo.auth.split(':')[1]);
useRedis = true;
}
let analytics = {};
let autosaveIntervalId;
const analyticsPath = process.env.SHIELDS_ANALYTICS_FILE || './analytics.json';
function performAutosave() {
const contents = JSON.stringify(analytics);
if (useRedis) {
redis.set(analyticsPath, contents);
} else {
fs.writeFileSync(analyticsPath, contents);
}
}
function scheduleAutosaving() {
const analyticsAutoSavePeriod = 10000;
autosaveIntervalId = setInterval(performAutosave, analyticsAutoSavePeriod);
}
// For a clean shutdown.
function cancelAutosaving() {
if (autosaveIntervalId) {
clearInterval(autosaveIntervalId);
autosaveIntervalId = null;
}
performAutosave();
}
function defaultAnalytics() {
const analytics = Object.create(null);
// In case something happens on the 36th.
analytics.vendorMonthly = new Array(36);
resetMonthlyAnalytics(analytics.vendorMonthly);
analytics.rawMonthly = new Array(36);
resetMonthlyAnalytics(analytics.rawMonthly);
analytics.vendorFlatMonthly = new Array(36);
resetMonthlyAnalytics(analytics.vendorFlatMonthly);
analytics.rawFlatMonthly = new Array(36);
resetMonthlyAnalytics(analytics.rawFlatMonthly);
analytics.vendorFlatSquareMonthly = new Array(36);
resetMonthlyAnalytics(analytics.vendorFlatSquareMonthly);
analytics.rawFlatSquareMonthly = new Array(36);
resetMonthlyAnalytics(analytics.rawFlatSquareMonthly);
return analytics;
}
function load() {
const defaultAnalyticsObject = defaultAnalytics();
if (useRedis) {
redis.get(analyticsPath, function(err, value) {
if (err == null && value != null) {
// if/try/return trick:
// if error, then the rest of the function is run.
try {
analytics = JSON.parse(value);
// Extend analytics with a new value.
for (const key in defaultAnalyticsObject) {
if (!(key in analytics)) {
analytics[key] = defaultAnalyticsObject[key];
}
}
return;
} catch(e) {
console.error('Invalid Redis analytics, resetting.');
console.error(e);
}
}
analytics = defaultAnalyticsObject;
});
} else {
// Not using Redis.
try {
analytics = JSON.parse(fs.readFileSync(analyticsPath));
// Extend analytics with a new value.
for (const key in defaultAnalyticsObject) {
if (!(key in analytics)) {
analytics[key] = defaultAnalyticsObject[key];
}
}
} catch(e) {
if (e.code !== 'ENOENT') {
console.error('Invalid JSON file for analytics, resetting.');
console.error(e);
}
analytics = defaultAnalyticsObject;
}
}
}
let lastDay = (new Date()).getDate();
function resetMonthlyAnalytics(monthlyAnalytics) {
for (let i = 0; i < monthlyAnalytics.length; i++) {
monthlyAnalytics[i] = 0;
}
}
function incrMonthlyAnalytics(monthlyAnalytics) {
try {
const currentDay = (new Date()).getDate();
// If we changed month, reset empty days.
while (lastDay !== currentDay) {
// Assumption: at least a hit a month.
lastDay = (lastDay + 1) % monthlyAnalytics.length;
monthlyAnalytics[lastDay] = 0;
}
monthlyAnalytics[currentDay]++;
} catch(e) { console.error(e.stack); }
}
function noteRequest(queryParams, match) {
incrMonthlyAnalytics(analytics.vendorMonthly);
if (queryParams.style === 'flat') {
incrMonthlyAnalytics(analytics.vendorFlatMonthly);
} else if (queryParams.style === 'flat-square') {
incrMonthlyAnalytics(analytics.vendorFlatSquareMonthly);
}
}
function setRoutes (server) {
server.ajax.on('analytics/v1', (json, end) => { end(analytics); });
}
module.exports = {
load,
scheduleAutosaving,
cancelAutosaving,
noteRequest,
setRoutes
};