mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 11:05:54 +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); | ||||
| 	} | ||||
|  | ||||
| 	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() { | ||||
| 		var trafficCanvas = document.getElementById('analytics-traffic'), | ||||
| 			registeredCanvas = document.getElementById('analytics-registered'), | ||||
| @@ -180,7 +154,7 @@ define('admin/general/dashboard', ['semver'], function(semver) { | ||||
| 			registeredCtx = registeredCanvas.getContext('2d'), | ||||
| 			presenceCtx = presenceCanvas.getContext('2d'), | ||||
| 			topicsCtx = topicsCanvas.getContext('2d'), | ||||
| 			trafficLabels = getHoursArray(); | ||||
| 			trafficLabels = utils.getHoursArray(); | ||||
|  | ||||
| 		if (isMobile) { | ||||
| 			Chart.defaults.global.showTooltips = false; | ||||
| @@ -325,9 +299,9 @@ define('admin/general/dashboard', ['semver'], function(semver) { | ||||
| 			} | ||||
|  | ||||
| 			if (units === 'days') { | ||||
| 				graphs.traffic.scale.xLabels = getDaysArray(until); | ||||
| 				graphs.traffic.scale.xLabels = utils.getDaysArray(until); | ||||
| 			} else { | ||||
| 				graphs.traffic.scale.xLabels = getHoursArray(); | ||||
| 				graphs.traffic.scale.xLabels = utils.getHoursArray(); | ||||
|  | ||||
| 				$('#pageViewsThisMonth').html(data.monthlyPageViews.thisMonth); | ||||
| 				$('#pageViewsLastMonth').html(data.monthlyPageViews.lastMonth); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| "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', [ | ||||
| 	'uploader', | ||||
| @@ -145,6 +145,12 @@ define('admin/manage/category', [ | ||||
| 		}); | ||||
|  | ||||
| 		Category.setupPrivilegeTable(); | ||||
| 		 | ||||
| 		if (window.location.hash === '#analytics') { | ||||
| 			Category.setupGraphs(); | ||||
| 		} else { | ||||
| 			$('a[href="#analytics"]').on('shown.bs.tab', Category.setupGraphs); | ||||
| 		} | ||||
| 	}; | ||||
|  | ||||
| 	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; | ||||
| }); | ||||
| @@ -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 | ||||
| 		params: function(options) { | ||||
| 			var a, hash = {}, params; | ||||
|   | ||||
							
								
								
									
										131
									
								
								src/analytics.js
									
									
									
									
									
								
							
							
						
						
									
										131
									
								
								src/analytics.js
									
									
									
									
									
								
							| @@ -1,23 +1,37 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| var cronJob = require('cron').CronJob; | ||||
| var async = require('async'); | ||||
|  | ||||
| var db = require('./database'); | ||||
|  | ||||
| (function(Analytics) { | ||||
| 	var counters = {}; | ||||
|  | ||||
| 	var pageViews = 0; | ||||
| 	var uniqueIPCount = 0; | ||||
| 	var uniquevisitors = 0; | ||||
|  | ||||
| 	var isCategory = /^(?:\/api)?\/category\/(\d+)/; | ||||
|  | ||||
| 	new cronJob('*/10 * * * *', function() { | ||||
| 		Analytics.writeData(); | ||||
| 	}, 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; | ||||
|  | ||||
| 		if (ip) { | ||||
| 			db.sortedSetScore('ip:recent', ip, function(err, score) { | ||||
| 		if (payload.ip) { | ||||
| 			db.sortedSetScore('ip:recent', payload.ip, function(err, score) { | ||||
| 				if (err) { | ||||
| 					return; | ||||
| 				} | ||||
| @@ -28,40 +42,116 @@ var db = require('./database'); | ||||
| 				today.setHours(today.getHours(), 0, 0, 0); | ||||
| 				if (!score || score < today.getTime()) { | ||||
| 					++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() { | ||||
| 		var today = new Date(); | ||||
| 		var month = new Date(); | ||||
| 		var dbQueue = []; | ||||
|  | ||||
| 		var today; | ||||
| 		if (pageViews > 0 || uniquevisitors > 0) { | ||||
| 			today = new Date(); | ||||
| 			today.setHours(today.getHours(), 0, 0, 0); | ||||
| 		} | ||||
| 		today.setHours(today.getHours(), 0, 0, 0); | ||||
| 		month.setMonth(month.getMonth(), 1); | ||||
| 		month.setHours(0, 0, 0, 0); | ||||
|  | ||||
| 		if (pageViews > 0) { | ||||
| 			db.sortedSetIncrBy('analytics:pageviews', pageViews, today.getTime()); | ||||
| 			var month = new Date(); | ||||
| 			month.setMonth(month.getMonth(), 1); | ||||
| 			month.setHours(0, 0, 0, 0); | ||||
| 			db.sortedSetIncrBy('analytics:pageviews:month', pageViews, month.getTime()); | ||||
| 			dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:pageviews', pageViews, today.getTime())); | ||||
| 			dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:pageviews:month', pageViews, month.getTime())); | ||||
| 			pageViews = 0; | ||||
| 		} | ||||
|  | ||||
| 		if (uniquevisitors > 0) { | ||||
| 			db.sortedSetIncrBy('analytics:uniquevisitors', uniquevisitors, today.getTime()); | ||||
| 			dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:uniquevisitors', uniquevisitors, today.getTime())); | ||||
| 			uniquevisitors = 0; | ||||
| 		} | ||||
|  | ||||
| 		if (uniqueIPCount > 0) { | ||||
| 			db.incrObjectFieldBy('global', 'uniqueIPCount', uniqueIPCount); | ||||
| 			dbQueue.push(async.apply(db.incrObjectFieldBy, 'global', 'uniqueIPCount', uniqueIPCount)); | ||||
| 			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() { | ||||
| @@ -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)); | ||||
| @@ -4,6 +4,7 @@ var async = require('async'), | ||||
| 	 | ||||
| 	categories = require('../../categories'), | ||||
| 	privileges = require('../../privileges'), | ||||
| 	analytics = require('../../analytics'), | ||||
| 	plugins = require('../../plugins'); | ||||
|  | ||||
|  | ||||
| @@ -12,20 +13,22 @@ var categoriesController = {}; | ||||
| categoriesController.get = function(req, res, next) { | ||||
| 	async.parallel({ | ||||
| 		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) { | ||||
| 		if (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) { | ||||
| 				return next(err); | ||||
| 			} | ||||
|  | ||||
| 			res.render('admin/manage/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.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}); | ||||
|  | ||||
|   | ||||
| @@ -217,16 +217,16 @@ SocketAdmin.analytics.get = function(socket, data, callback) { | ||||
| 			async.parallel({ | ||||
| 				uniqueVisitors: function(next) { | ||||
| 					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 { | ||||
| 						getHourlyStatsForSet('analytics:uniquevisitors', data.until || Date.now(), data.amount, next); | ||||
| 						analytics.getHourlyStatsForSet('analytics:uniquevisitors', data.until || Date.now(), data.amount, next); | ||||
| 					} | ||||
| 				}, | ||||
| 				pageviews: function(next) { | ||||
| 					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 { | ||||
| 						getHourlyStatsForSet('analytics:pageviews', data.until || Date.now(), data.amount, next); | ||||
| 						analytics.getHourlyStatsForSet('analytics:pageviews', data.until || Date.now(), data.amount, next); | ||||
| 					} | ||||
| 				}, | ||||
| 				monthlyPageViews: function(next) { | ||||
| @@ -251,62 +251,6 @@ SocketAdmin.logs.clear = function(socket, data, 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) { | ||||
| 	var start = parseInt(next, 10); | ||||
| 	if (start < 0) { | ||||
|   | ||||
| @@ -6,6 +6,7 @@ var async = require('async'), | ||||
| 	db = require('../database'), | ||||
| 	utils = require('../../public/src/utils'), | ||||
| 	plugins = require('../plugins'), | ||||
| 	analytics = require('../analytics'), | ||||
| 	user = require('../user'), | ||||
| 	meta = require('../meta'), | ||||
| 	posts = require('../posts'), | ||||
| @@ -15,7 +16,7 @@ var async = require('async'), | ||||
| module.exports = function(Topics) { | ||||
|  | ||||
| 	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 topicData; | ||||
|  | ||||
| @@ -171,6 +172,7 @@ module.exports = function(Topics) { | ||||
| 				data.topicData.mainPost = data.postData; | ||||
| 				data.postData.index = 0; | ||||
|  | ||||
| 				analytics.increment(['topics', 'topics:byCid:' + data.topicData.cid]); | ||||
| 				plugins.fireHook('action:topic.post', data.topicData); | ||||
|  | ||||
| 				if (parseInt(uid, 10)) { | ||||
| @@ -256,6 +258,7 @@ module.exports = function(Topics) { | ||||
| 				} | ||||
|  | ||||
| 				Topics.notifyFollowers(postData, uid); | ||||
| 				analytics.increment(['posts', 'posts:byCid:' + cid]); | ||||
| 				plugins.fireHook('action:topic.reply', postData); | ||||
|  | ||||
| 				next(null, postData); | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| 		<ul class="nav nav-pills"> | ||||
| 			<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="#analytics" data-toggle="tab">Analytics</a></li> | ||||
| 		</ul> | ||||
| 		<br /> | ||||
| 		<div class="tab-content"> | ||||
| @@ -119,6 +120,37 @@ | ||||
| 					<!-- IMPORT admin/partials/categories/privileges.tpl --> | ||||
| 				</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> | ||||
| 	</form> | ||||
| </div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user