mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-01 19:46:01 +01:00
Squashed commit of the following:
commit 49e6c0040cc82c1e2684933a8e167ef14854aff8 Author: Julian Lam <julian@designcreateplay.com> Date: Thu Feb 25 16:12:15 2016 -0500 added recording and charts for topic and post counts globally and by cid commit e02ff70757f778aa016fbc42ef10a5da2d07a9d9 Author: Julian Lam <julian@designcreateplay.com> Date: Thu Feb 25 15:35:49 2016 -0500 added labels to charts commit e75d83bf3886e5183bcf5fcd848d71c513761e01 Author: Julian Lam <julian@designcreateplay.com> Date: Thu Feb 25 13:30:47 2016 -0500 added per category graphs to ACP management page commit e3f543200950925cc9e8bf33cccb592f949a100e Author: Julian Lam <julian@designcreateplay.com> Date: Thu Feb 25 12:36:11 2016 -0500 updated analytics to move helper methods to analytics lib and sending per category analytics to ACP page commit 01891d8f7c408925fcdad18dcaa941e5ebbeb9b2 Author: Julian Lam <julian@designcreateplay.com> Date: Wed Feb 24 16:48:55 2016 -0500 saving per-category analytics, and updated the writeData method to use async for "clarity"
This commit is contained in:
@@ -145,32 +145,6 @@ define('admin/general/dashboard', ['semver'], function(semver) {
|
|||||||
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
|
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHoursArray() {
|
|
||||||
var currentHour = new Date().getHours(),
|
|
||||||
labels = [];
|
|
||||||
|
|
||||||
for (var i = currentHour, ii = currentHour - 24; i > ii; i--) {
|
|
||||||
var hour = i < 0 ? 24 + i : i;
|
|
||||||
labels.push(hour + ':00');
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDaysArray(from) {
|
|
||||||
var currentDay = new Date(from || Date.now()).getTime(),
|
|
||||||
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
|
||||||
labels = [],
|
|
||||||
tmpDate;
|
|
||||||
|
|
||||||
for(var x=29;x>=0;x--) {
|
|
||||||
tmpDate = new Date(currentDay - (1000*60*60*24*x));
|
|
||||||
labels.push(months[tmpDate.getMonth()] + ' ' + tmpDate.getDate());
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupGraphs() {
|
function setupGraphs() {
|
||||||
var trafficCanvas = document.getElementById('analytics-traffic'),
|
var trafficCanvas = document.getElementById('analytics-traffic'),
|
||||||
registeredCanvas = document.getElementById('analytics-registered'),
|
registeredCanvas = document.getElementById('analytics-registered'),
|
||||||
@@ -180,7 +154,7 @@ define('admin/general/dashboard', ['semver'], function(semver) {
|
|||||||
registeredCtx = registeredCanvas.getContext('2d'),
|
registeredCtx = registeredCanvas.getContext('2d'),
|
||||||
presenceCtx = presenceCanvas.getContext('2d'),
|
presenceCtx = presenceCanvas.getContext('2d'),
|
||||||
topicsCtx = topicsCanvas.getContext('2d'),
|
topicsCtx = topicsCanvas.getContext('2d'),
|
||||||
trafficLabels = getHoursArray();
|
trafficLabels = utils.getHoursArray();
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
Chart.defaults.global.showTooltips = false;
|
Chart.defaults.global.showTooltips = false;
|
||||||
@@ -325,9 +299,9 @@ define('admin/general/dashboard', ['semver'], function(semver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (units === 'days') {
|
if (units === 'days') {
|
||||||
graphs.traffic.scale.xLabels = getDaysArray(until);
|
graphs.traffic.scale.xLabels = utils.getDaysArray(until);
|
||||||
} else {
|
} else {
|
||||||
graphs.traffic.scale.xLabels = getHoursArray();
|
graphs.traffic.scale.xLabels = utils.getHoursArray();
|
||||||
|
|
||||||
$('#pageViewsThisMonth').html(data.monthlyPageViews.thisMonth);
|
$('#pageViewsThisMonth').html(data.monthlyPageViews.thisMonth);
|
||||||
$('#pageViewsLastMonth').html(data.monthlyPageViews.lastMonth);
|
$('#pageViewsLastMonth').html(data.monthlyPageViews.lastMonth);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
/*global define, app, socket, ajaxify, RELATIVE_PATH, bootbox, templates */
|
/*global define, app, socket, ajaxify, RELATIVE_PATH, bootbox, templates, Chart */
|
||||||
|
|
||||||
define('admin/manage/category', [
|
define('admin/manage/category', [
|
||||||
'uploader',
|
'uploader',
|
||||||
@@ -145,6 +145,12 @@ define('admin/manage/category', [
|
|||||||
});
|
});
|
||||||
|
|
||||||
Category.setupPrivilegeTable();
|
Category.setupPrivilegeTable();
|
||||||
|
|
||||||
|
if (window.location.hash === '#analytics') {
|
||||||
|
Category.setupGraphs();
|
||||||
|
} else {
|
||||||
|
$('a[href="#analytics"]').on('shown.bs.tab', Category.setupGraphs);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Category.setupPrivilegeTable = function() {
|
Category.setupPrivilegeTable = function() {
|
||||||
@@ -345,5 +351,106 @@ define('admin/manage/category', [
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Category.setupGraphs = function() {
|
||||||
|
var hourlyCanvas = document.getElementById('pageviews:hourly'),
|
||||||
|
dailyCanvas = document.getElementById('pageviews:daily'),
|
||||||
|
topicsCanvas = document.getElementById('topics:daily'),
|
||||||
|
postsCanvas = document.getElementById('posts:daily'),
|
||||||
|
hourlyLabels = utils.getHoursArray().map(function(text, idx) {
|
||||||
|
return idx % 3 ? '' : text;
|
||||||
|
}),
|
||||||
|
dailyLabels = utils.getDaysArray().map(function(text, idx) {
|
||||||
|
return idx % 3 ? '' : text;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (utils.isMobile()) {
|
||||||
|
Chart.defaults.global.showTooltips = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
'pageviews:hourly': {
|
||||||
|
labels: hourlyLabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "",
|
||||||
|
fillColor: "rgba(220,220,220,0.2)",
|
||||||
|
strokeColor: "rgba(220,220,220,1)",
|
||||||
|
pointColor: "rgba(220,220,220,1)",
|
||||||
|
pointStrokeColor: "#fff",
|
||||||
|
pointHighlightFill: "#fff",
|
||||||
|
pointHighlightStroke: "rgba(220,220,220,1)",
|
||||||
|
data: ajaxify.data.analytics['pageviews:hourly']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'pageviews:daily': {
|
||||||
|
labels: dailyLabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "",
|
||||||
|
fillColor: "rgba(151,187,205,0.2)",
|
||||||
|
strokeColor: "rgba(151,187,205,1)",
|
||||||
|
pointColor: "rgba(151,187,205,1)",
|
||||||
|
pointStrokeColor: "#fff",
|
||||||
|
pointHighlightFill: "#fff",
|
||||||
|
pointHighlightStroke: "rgba(151,187,205,1)",
|
||||||
|
data: ajaxify.data.analytics['pageviews:daily']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'topics:daily': {
|
||||||
|
labels: dailyLabels.slice(-7),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "",
|
||||||
|
fillColor: "rgba(151,187,205,0.2)",
|
||||||
|
strokeColor: "rgba(151,187,205,1)",
|
||||||
|
pointColor: "rgba(151,187,205,1)",
|
||||||
|
pointStrokeColor: "#fff",
|
||||||
|
pointHighlightFill: "#fff",
|
||||||
|
pointHighlightStroke: "rgba(151,187,205,1)",
|
||||||
|
data: ajaxify.data.analytics['topics:daily']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'posts:daily': {
|
||||||
|
labels: dailyLabels.slice(-7),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "",
|
||||||
|
fillColor: "rgba(151,187,205,0.2)",
|
||||||
|
strokeColor: "rgba(151,187,205,1)",
|
||||||
|
pointColor: "rgba(151,187,205,1)",
|
||||||
|
pointStrokeColor: "#fff",
|
||||||
|
pointHighlightFill: "#fff",
|
||||||
|
pointHighlightStroke: "rgba(151,187,205,1)",
|
||||||
|
data: ajaxify.data.analytics['posts:daily']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
hourlyCanvas.width = $(hourlyCanvas).parent().width();
|
||||||
|
dailyCanvas.width = $(dailyCanvas).parent().width();
|
||||||
|
topicsCanvas.width = $(topicsCanvas).parent().width();
|
||||||
|
postsCanvas.width = $(postsCanvas).parent().width();
|
||||||
|
new Chart(hourlyCanvas.getContext('2d')).Line(data['pageviews:hourly'], {
|
||||||
|
responsive: true,
|
||||||
|
animation: false
|
||||||
|
});
|
||||||
|
new Chart(dailyCanvas.getContext('2d')).Line(data['pageviews:daily'], {
|
||||||
|
responsive: true,
|
||||||
|
animation: false
|
||||||
|
});
|
||||||
|
new Chart(topicsCanvas.getContext('2d')).Line(data['topics:daily'], {
|
||||||
|
responsive: true,
|
||||||
|
animation: false
|
||||||
|
});
|
||||||
|
new Chart(postsCanvas.getContext('2d')).Line(data['posts:daily'], {
|
||||||
|
responsive: true,
|
||||||
|
animation: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return Category;
|
return Category;
|
||||||
});
|
});
|
||||||
@@ -259,6 +259,39 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isMobile: function() {
|
||||||
|
var env = utils.findBootstrapEnvironment();
|
||||||
|
return ['xs', 'sm'].some(function(targetEnv) {
|
||||||
|
return targetEnv === env;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getHoursArray: function() {
|
||||||
|
var currentHour = new Date().getHours(),
|
||||||
|
labels = [];
|
||||||
|
|
||||||
|
for (var i = currentHour, ii = currentHour - 24; i > ii; i--) {
|
||||||
|
var hour = i < 0 ? 24 + i : i;
|
||||||
|
labels.push(hour + ':00');
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels.reverse();
|
||||||
|
},
|
||||||
|
|
||||||
|
getDaysArray: function(from) {
|
||||||
|
var currentDay = new Date(from || Date.now()).getTime(),
|
||||||
|
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||||
|
labels = [],
|
||||||
|
tmpDate;
|
||||||
|
|
||||||
|
for(var x=29;x>=0;x--) {
|
||||||
|
tmpDate = new Date(currentDay - (1000*60*60*24*x));
|
||||||
|
labels.push(months[tmpDate.getMonth()] + ' ' + tmpDate.getDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
},
|
||||||
|
|
||||||
// get all the url params in a single key/value hash
|
// get all the url params in a single key/value hash
|
||||||
params: function(options) {
|
params: function(options) {
|
||||||
var a, hash = {}, params;
|
var a, hash = {}, params;
|
||||||
|
|||||||
131
src/analytics.js
131
src/analytics.js
@@ -1,23 +1,37 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var cronJob = require('cron').CronJob;
|
var cronJob = require('cron').CronJob;
|
||||||
|
var async = require('async');
|
||||||
|
|
||||||
var db = require('./database');
|
var db = require('./database');
|
||||||
|
|
||||||
(function(Analytics) {
|
(function(Analytics) {
|
||||||
|
var counters = {};
|
||||||
|
|
||||||
var pageViews = 0;
|
var pageViews = 0;
|
||||||
var uniqueIPCount = 0;
|
var uniqueIPCount = 0;
|
||||||
var uniquevisitors = 0;
|
var uniquevisitors = 0;
|
||||||
|
|
||||||
|
var isCategory = /^(?:\/api)?\/category\/(\d+)/;
|
||||||
|
|
||||||
new cronJob('*/10 * * * *', function() {
|
new cronJob('*/10 * * * *', function() {
|
||||||
Analytics.writeData();
|
Analytics.writeData();
|
||||||
}, null, true);
|
}, null, true);
|
||||||
|
|
||||||
Analytics.pageView = function(ip) {
|
Analytics.increment = function(keys) {
|
||||||
|
keys = Array.isArray(keys) ? keys : [keys];
|
||||||
|
|
||||||
|
keys.forEach(function(key) {
|
||||||
|
counters[key] = counters[key] || 0;
|
||||||
|
++counters[key];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Analytics.pageView = function(payload) {
|
||||||
++pageViews;
|
++pageViews;
|
||||||
|
|
||||||
if (ip) {
|
if (payload.ip) {
|
||||||
db.sortedSetScore('ip:recent', ip, function(err, score) {
|
db.sortedSetScore('ip:recent', payload.ip, function(err, score) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -28,40 +42,116 @@ var db = require('./database');
|
|||||||
today.setHours(today.getHours(), 0, 0, 0);
|
today.setHours(today.getHours(), 0, 0, 0);
|
||||||
if (!score || score < today.getTime()) {
|
if (!score || score < today.getTime()) {
|
||||||
++uniquevisitors;
|
++uniquevisitors;
|
||||||
db.sortedSetAdd('ip:recent', Date.now(), ip);
|
db.sortedSetAdd('ip:recent', Date.now(), payload.ip);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payload.path) {
|
||||||
|
var categoryMatch = payload.path.match(isCategory),
|
||||||
|
cid = categoryMatch ? parseInt(categoryMatch[1], 10) : null;
|
||||||
|
|
||||||
|
if (cid) {
|
||||||
|
Analytics.increment(['pageviews:byCid:' + cid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Analytics.writeData = function() {
|
Analytics.writeData = function() {
|
||||||
|
var today = new Date();
|
||||||
|
var month = new Date();
|
||||||
|
var dbQueue = [];
|
||||||
|
|
||||||
var today;
|
today.setHours(today.getHours(), 0, 0, 0);
|
||||||
if (pageViews > 0 || uniquevisitors > 0) {
|
month.setMonth(month.getMonth(), 1);
|
||||||
today = new Date();
|
month.setHours(0, 0, 0, 0);
|
||||||
today.setHours(today.getHours(), 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pageViews > 0) {
|
if (pageViews > 0) {
|
||||||
db.sortedSetIncrBy('analytics:pageviews', pageViews, today.getTime());
|
dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:pageviews', pageViews, today.getTime()));
|
||||||
var month = new Date();
|
dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:pageviews:month', pageViews, month.getTime()));
|
||||||
month.setMonth(month.getMonth(), 1);
|
|
||||||
month.setHours(0, 0, 0, 0);
|
|
||||||
db.sortedSetIncrBy('analytics:pageviews:month', pageViews, month.getTime());
|
|
||||||
pageViews = 0;
|
pageViews = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uniquevisitors > 0) {
|
if (uniquevisitors > 0) {
|
||||||
db.sortedSetIncrBy('analytics:uniquevisitors', uniquevisitors, today.getTime());
|
dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:uniquevisitors', uniquevisitors, today.getTime()));
|
||||||
uniquevisitors = 0;
|
uniquevisitors = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uniqueIPCount > 0) {
|
if (uniqueIPCount > 0) {
|
||||||
db.incrObjectFieldBy('global', 'uniqueIPCount', uniqueIPCount);
|
dbQueue.push(async.apply(db.incrObjectFieldBy, 'global', 'uniqueIPCount', uniqueIPCount));
|
||||||
uniqueIPCount = 0;
|
uniqueIPCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.keys(counters).length > 0) {
|
||||||
|
for(var key in counters) {
|
||||||
|
console.log('flushing', key, 'with a value of', counters[key]);
|
||||||
|
dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:' + key, counters[key], today.getTime()));
|
||||||
|
delete counters[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async.parallel(dbQueue, function(err) {
|
||||||
|
if (err) {
|
||||||
|
winston.error('[analytics] Encountered error while writing analytics to data store: ' + err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Analytics.getHourlyStatsForSet = function(set, hour, numHours, callback) {
|
||||||
|
var terms = {},
|
||||||
|
hoursArr = [];
|
||||||
|
|
||||||
|
hour = new Date(hour);
|
||||||
|
hour.setHours(hour.getHours(), 0, 0, 0);
|
||||||
|
|
||||||
|
for (var i = 0, ii = numHours; i < ii; i++) {
|
||||||
|
hoursArr.push(hour.getTime());
|
||||||
|
hour.setHours(hour.getHours() - 1, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
db.sortedSetScores(set, hoursArr, function(err, counts) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
hoursArr.forEach(function(term, index) {
|
||||||
|
terms[term] = parseInt(counts[index], 10) || 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
var termsArr = [];
|
||||||
|
|
||||||
|
hoursArr.reverse();
|
||||||
|
hoursArr.forEach(function(hour) {
|
||||||
|
termsArr.push(terms[hour]);
|
||||||
|
});
|
||||||
|
|
||||||
|
callback(null, termsArr);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Analytics.getDailyStatsForSet = function(set, day, numDays, callback) {
|
||||||
|
var daysArr = [];
|
||||||
|
|
||||||
|
day = new Date(day);
|
||||||
|
day.setDate(day.getDate()+1); // set the date to tomorrow, because getHourlyStatsForSet steps *backwards* 24 hours to sum up the values
|
||||||
|
day.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
async.whilst(function() {
|
||||||
|
return numDays--;
|
||||||
|
}, function(next) {
|
||||||
|
Analytics.getHourlyStatsForSet(set, day.getTime()-(1000*60*60*24*numDays), 24, function(err, day) {
|
||||||
|
if (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
daysArr.push(day.reduce(function(cur, next) {
|
||||||
|
return cur+next;
|
||||||
|
}));
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}, function(err) {
|
||||||
|
callback(err, daysArr);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Analytics.getUnwrittenPageviews = function() {
|
Analytics.getUnwrittenPageviews = function() {
|
||||||
@@ -86,4 +176,13 @@ var db = require('./database');
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Analytics.getCategoryAnalytics = function(cid, callback) {
|
||||||
|
async.parallel({
|
||||||
|
'pageviews:hourly': async.apply(Analytics.getHourlyStatsForSet, 'analytics:pageviews:byCid:' + cid, Date.now(), 24),
|
||||||
|
'pageviews:daily': async.apply(Analytics.getDailyStatsForSet, 'analytics:pageviews:byCid:' + cid, Date.now(), 30),
|
||||||
|
'topics:daily': async.apply(Analytics.getDailyStatsForSet, 'analytics:topics:byCid:' + cid, Date.now(), 7),
|
||||||
|
'posts:daily': async.apply(Analytics.getDailyStatsForSet, 'analytics:posts:byCid:' + cid, Date.now(), 7),
|
||||||
|
}, callback);
|
||||||
|
};
|
||||||
|
|
||||||
}(exports));
|
}(exports));
|
||||||
@@ -4,6 +4,7 @@ var async = require('async'),
|
|||||||
|
|
||||||
categories = require('../../categories'),
|
categories = require('../../categories'),
|
||||||
privileges = require('../../privileges'),
|
privileges = require('../../privileges'),
|
||||||
|
analytics = require('../../analytics'),
|
||||||
plugins = require('../../plugins');
|
plugins = require('../../plugins');
|
||||||
|
|
||||||
|
|
||||||
@@ -12,20 +13,22 @@ var categoriesController = {};
|
|||||||
categoriesController.get = function(req, res, next) {
|
categoriesController.get = function(req, res, next) {
|
||||||
async.parallel({
|
async.parallel({
|
||||||
category: async.apply(categories.getCategories, [req.params.category_id], req.user.uid),
|
category: async.apply(categories.getCategories, [req.params.category_id], req.user.uid),
|
||||||
privileges: async.apply(privileges.categories.list, req.params.category_id)
|
privileges: async.apply(privileges.categories.list, req.params.category_id),
|
||||||
|
analytics: async.apply(analytics.getCategoryAnalytics, req.params.category_id)
|
||||||
}, function(err, data) {
|
}, function(err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins.fireHook('filter:admin.category.get', {req: req, res: res, category: data.category[0], privileges: data.privileges}, function(err, data) {
|
plugins.fireHook('filter:admin.category.get', { req: req, res: res, category: data.category[0], privileges: data.privileges, analytics: data.analytics }, function(err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.render('admin/manage/category', {
|
res.render('admin/manage/category', {
|
||||||
category: data.category,
|
category: data.category,
|
||||||
privileges: data.privileges
|
privileges: data.privileges,
|
||||||
|
analytics: data.analytics
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -48,7 +48,11 @@ middleware.applyCSRF = csrf();
|
|||||||
middleware.ensureLoggedIn = ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login');
|
middleware.ensureLoggedIn = ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login');
|
||||||
|
|
||||||
middleware.pageView = function(req, res, next) {
|
middleware.pageView = function(req, res, next) {
|
||||||
analytics.pageView(req.ip);
|
analytics.pageView({
|
||||||
|
ip: req.ip,
|
||||||
|
path: req.path,
|
||||||
|
uid: req.hasOwnProperty('user') && req.user.hasOwnProperty('uid') ? parseInt(req.user.uid, 10) : 0
|
||||||
|
});
|
||||||
|
|
||||||
plugins.fireHook('action:middleware.pageView', {req: req});
|
plugins.fireHook('action:middleware.pageView', {req: req});
|
||||||
|
|
||||||
|
|||||||
@@ -217,16 +217,16 @@ SocketAdmin.analytics.get = function(socket, data, callback) {
|
|||||||
async.parallel({
|
async.parallel({
|
||||||
uniqueVisitors: function(next) {
|
uniqueVisitors: function(next) {
|
||||||
if (data.units === 'days') {
|
if (data.units === 'days') {
|
||||||
getDailyStatsForSet('analytics:uniquevisitors', data.until || Date.now(), data.amount, next);
|
analytics.getDailyStatsForSet('analytics:uniquevisitors', data.until || Date.now(), data.amount, next);
|
||||||
} else {
|
} else {
|
||||||
getHourlyStatsForSet('analytics:uniquevisitors', data.until || Date.now(), data.amount, next);
|
analytics.getHourlyStatsForSet('analytics:uniquevisitors', data.until || Date.now(), data.amount, next);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pageviews: function(next) {
|
pageviews: function(next) {
|
||||||
if (data.units === 'days') {
|
if (data.units === 'days') {
|
||||||
getDailyStatsForSet('analytics:pageviews', data.until || Date.now(), data.amount, next);
|
analytics.getDailyStatsForSet('analytics:pageviews', data.until || Date.now(), data.amount, next);
|
||||||
} else {
|
} else {
|
||||||
getHourlyStatsForSet('analytics:pageviews', data.until || Date.now(), data.amount, next);
|
analytics.getHourlyStatsForSet('analytics:pageviews', data.until || Date.now(), data.amount, next);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
monthlyPageViews: function(next) {
|
monthlyPageViews: function(next) {
|
||||||
@@ -251,62 +251,6 @@ SocketAdmin.logs.clear = function(socket, data, callback) {
|
|||||||
meta.logs.clear(callback);
|
meta.logs.clear(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
function getHourlyStatsForSet(set, hour, numHours, callback) {
|
|
||||||
var terms = {},
|
|
||||||
hoursArr = [];
|
|
||||||
|
|
||||||
hour = new Date(hour);
|
|
||||||
hour.setHours(hour.getHours(), 0, 0, 0);
|
|
||||||
|
|
||||||
for (var i = 0, ii = numHours; i < ii; i++) {
|
|
||||||
hoursArr.push(hour.getTime());
|
|
||||||
hour.setHours(hour.getHours() - 1, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
db.sortedSetScores(set, hoursArr, function(err, counts) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
hoursArr.forEach(function(term, index) {
|
|
||||||
terms[term] = parseInt(counts[index], 10) || 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
var termsArr = [];
|
|
||||||
|
|
||||||
hoursArr.reverse();
|
|
||||||
hoursArr.forEach(function(hour) {
|
|
||||||
termsArr.push(terms[hour]);
|
|
||||||
});
|
|
||||||
|
|
||||||
callback(null, termsArr);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDailyStatsForSet(set, day, numDays, callback) {
|
|
||||||
var daysArr = [];
|
|
||||||
|
|
||||||
day = new Date(day);
|
|
||||||
day.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
async.whilst(function() {
|
|
||||||
return numDays--;
|
|
||||||
}, function(next) {
|
|
||||||
getHourlyStatsForSet(set, day.getTime()-(1000*60*60*24*numDays), 24, function(err, day) {
|
|
||||||
if (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
daysArr.push(day.reduce(function(cur, next) {
|
|
||||||
return cur+next;
|
|
||||||
}));
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}, function(err) {
|
|
||||||
callback(err, daysArr);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketAdmin.getMoreEvents = function(socket, next, callback) {
|
SocketAdmin.getMoreEvents = function(socket, next, callback) {
|
||||||
var start = parseInt(next, 10);
|
var start = parseInt(next, 10);
|
||||||
if (start < 0) {
|
if (start < 0) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ var async = require('async'),
|
|||||||
db = require('../database'),
|
db = require('../database'),
|
||||||
utils = require('../../public/src/utils'),
|
utils = require('../../public/src/utils'),
|
||||||
plugins = require('../plugins'),
|
plugins = require('../plugins'),
|
||||||
|
analytics = require('../analytics'),
|
||||||
user = require('../user'),
|
user = require('../user'),
|
||||||
meta = require('../meta'),
|
meta = require('../meta'),
|
||||||
posts = require('../posts'),
|
posts = require('../posts'),
|
||||||
@@ -15,7 +16,7 @@ var async = require('async'),
|
|||||||
module.exports = function(Topics) {
|
module.exports = function(Topics) {
|
||||||
|
|
||||||
Topics.create = function(data, callback) {
|
Topics.create = function(data, callback) {
|
||||||
// This is an interal method, consider using Topics.post instead
|
// This is an internal method, consider using Topics.post instead
|
||||||
var timestamp = data.timestamp || Date.now();
|
var timestamp = data.timestamp || Date.now();
|
||||||
var topicData;
|
var topicData;
|
||||||
|
|
||||||
@@ -171,6 +172,7 @@ module.exports = function(Topics) {
|
|||||||
data.topicData.mainPost = data.postData;
|
data.topicData.mainPost = data.postData;
|
||||||
data.postData.index = 0;
|
data.postData.index = 0;
|
||||||
|
|
||||||
|
analytics.increment(['topics', 'topics:byCid:' + data.topicData.cid]);
|
||||||
plugins.fireHook('action:topic.post', data.topicData);
|
plugins.fireHook('action:topic.post', data.topicData);
|
||||||
|
|
||||||
if (parseInt(uid, 10)) {
|
if (parseInt(uid, 10)) {
|
||||||
@@ -256,6 +258,7 @@ module.exports = function(Topics) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Topics.notifyFollowers(postData, uid);
|
Topics.notifyFollowers(postData, uid);
|
||||||
|
analytics.increment(['posts', 'posts:byCid:' + cid]);
|
||||||
plugins.fireHook('action:topic.reply', postData);
|
plugins.fireHook('action:topic.reply', postData);
|
||||||
|
|
||||||
next(null, postData);
|
next(null, postData);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<ul class="nav nav-pills">
|
<ul class="nav nav-pills">
|
||||||
<li class="active"><a href="#category-settings" data-toggle="tab">Category Settings</a></li>
|
<li class="active"><a href="#category-settings" data-toggle="tab">Category Settings</a></li>
|
||||||
<li><a href="#privileges" data-toggle="tab">Privileges</a></li>
|
<li><a href="#privileges" data-toggle="tab">Privileges</a></li>
|
||||||
|
<li><a href="#analytics" data-toggle="tab">Analytics</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<br />
|
<br />
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
@@ -119,6 +120,37 @@
|
|||||||
<!-- IMPORT admin/partials/categories/privileges.tpl -->
|
<!-- IMPORT admin/partials/categories/privileges.tpl -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade col-xs-12" id="analytics">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6 text-center">
|
||||||
|
<div><canvas id="pageviews:hourly" height="250"></canvas></div>
|
||||||
|
<p>
|
||||||
|
<small><strong>Figure 1</strong> – Hourly page views for this category</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 text-center">
|
||||||
|
<div><canvas id="pageviews:daily" height="250"></canvas></div>
|
||||||
|
<p>
|
||||||
|
<small><strong>Figure 2</strong> – Daily page views for this category</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6 text-center">
|
||||||
|
<div><canvas id="topics:daily" height="250"></canvas></div>
|
||||||
|
<p>
|
||||||
|
<small><strong>Figure 3</strong> – Daily topics created in this category</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 text-center">
|
||||||
|
<div><canvas id="posts:daily" height="250"></canvas></div>
|
||||||
|
<p>
|
||||||
|
<small><strong>Figure 4</strong> – Daily posts made in this category</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user