feat: ACP analytics API route (#7725)

* feat: added API route for retrieving analytics via REST API

* feat: sets is now optional, can pass in multiple sets

* fix: moved expand and added json button to panel header

* fix: matching api params to socket method

* fix: update json api button url on graph change

* fix: updated default counts based on passed in units
v1.18.x
Julian Lam 6 years ago committed by GitHub
parent fb0870297b
commit a0c0ef1ba4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,16 +5,20 @@
max-width: 100% !important; max-width: 100% !important;
} }
.graph-container { #analytics-panel .panel-heading i {
padding-right: 50px; &.fa-expand {
position: relative;
background: @body-bg;
.fa-expand {
display: none; display: none;
position: absolute; }
right: 20px;
padding: 5px; &.fa-terminal::after {
content: 'JSON';
font-family: @font-family-sans-serif;
font-weight: 600;
color: @gray-dark;
padding-left: .5em;
}
padding: .75em;
background-color: @gray-lighter; background-color: @gray-lighter;
color: @gray-base; color: @gray-base;
cursor: pointer; cursor: pointer;
@ -25,6 +29,11 @@
} }
} }
.graph-container {
padding-right: 50px;
position: relative;
background: @body-bg;
&:hover { &:hover {
.fa-expand { .fa-expand {
color: @gray-lighter; color: @gray-lighter;

@ -461,6 +461,15 @@ define('admin/general/dashboard', ['semver', 'Chart', 'translator', 'benchpress'
currentGraph.units = units; currentGraph.units = units;
currentGraph.until = until; currentGraph.until = until;
currentGraph.amount = amount; currentGraph.amount = amount;
// Update the View as JSON button url
var apiEl = $('#view-as-json');
var newHref = $.param({
units: units,
until: until,
count: amount,
});
apiEl.attr('href', config.relative_path + '/api/admin/analytics?' + newHref);
}); });
} }
@ -556,7 +565,7 @@ define('admin/general/dashboard', ['semver', 'Chart', 'translator', 'benchpress'
} }
function setupFullscreen() { function setupFullscreen() {
var container = document.getElementById('analytics-traffic-container'); var container = document.getElementById('analytics-panel');
var $container = $(container); var $container = $(container);
var btn = $container.find('.fa-expand'); var btn = $container.find('.fa-expand');
var fsMethod; var fsMethod;

@ -259,3 +259,5 @@ Analytics.getBlacklistAnalytics = function (callback) {
hourly: async.apply(Analytics.getHourlyStatsForSet, 'analytics:blacklist', Date.now(), 24), hourly: async.apply(Analytics.getHourlyStatsForSet, 'analytics:blacklist', Date.now(), 24),
}, callback); }, callback);
}; };
Analytics.async = require('./promisify')(Analytics);

@ -4,10 +4,12 @@ var async = require('async');
var nconf = require('nconf'); var nconf = require('nconf');
var semver = require('semver'); var semver = require('semver');
var winston = require('winston'); var winston = require('winston');
const _ = require('lodash');
var versions = require('../../admin/versions'); var versions = require('../../admin/versions');
var db = require('../../database'); var db = require('../../database');
var meta = require('../../meta'); var meta = require('../../meta');
const analytics = require('../../analytics').async;
var plugins = require('../../plugins'); var plugins = require('../../plugins');
var user = require('../../user'); var user = require('../../user');
var utils = require('../../utils'); var utils = require('../../utils');
@ -76,6 +78,40 @@ dashboardController.get = function (req, res, next) {
], next); ], next);
}; };
dashboardController.getAnalytics = async (req, res, next) => {
// Basic validation
const validUnits = ['days', 'hours'];
const validSets = ['uniquevisitors', 'pageviews', 'pageviews:registered', 'pageviews:bot', 'pageviews:guest'];
const until = req.query.until ? new Date(parseInt(req.query.until, 10)) : Date.now();
const count = req.query.count || (req.query.units === 'hours' ? 24 : 30);
if (isNaN(until) || !validUnits.includes(req.query.units)) {
return next(new Error('[[error:invalid-data]]'));
}
// Filter out invalid sets, if no sets, assume all sets
let sets;
if (req.query.sets) {
sets = Array.isArray(req.query.sets) ? req.query.sets : [req.query.sets];
sets = sets.filter(set => validSets.includes(set));
} else {
sets = validSets;
}
const method = req.query.units === 'days' ? analytics.getDailyStatsForSet : analytics.getHourlyStatsForSet;
let payload = await Promise.all(sets.map(async set => method('analytics:' + set, until, count)));
payload = _.zipObject(sets, payload);
res.json({
query: {
set: req.query.set,
units: req.query.units,
until: until,
count: count,
},
result: payload,
});
};
function getStats(callback) { function getStats(callback) {
async.waterfall([ async.waterfall([
function (next) { function (next) {

@ -5,6 +5,7 @@ var express = require('express');
function apiRoutes(router, middleware, controllers) { function apiRoutes(router, middleware, controllers) {
router.get('/users/csv', middleware.authenticate, controllers.admin.users.getCSV); router.get('/users/csv', middleware.authenticate, controllers.admin.users.getCSV);
router.get('/analytics', middleware.authenticate, controllers.admin.dashboard.getAnalytics);
var multipart = require('connect-multiparty'); var multipart = require('connect-multiparty');
var multipartMiddleware = multipart(); var multipartMiddleware = multipart();

@ -1,10 +1,15 @@
<div class="row dashboard"> <div class="row dashboard">
<div class="col-lg-9"> <div class="col-lg-9">
<div class="panel panel-default"> <div class="panel panel-default" id="analytics-panel">
<div class="panel-heading">[[admin/general/dashboard:forum-traffic]]</div> <div class="panel-heading">
[[admin/general/dashboard:forum-traffic]]
<div class="pull-right">
<a id="view-as-json" href="{config.relative_path}/api/admin/analytics&type=hourly"><i class="fa fa-terminal"></i></a>
<i class="fa fa-expand"></i>
</div>
</div>
<div class="panel-body"> <div class="panel-body">
<div class="graph-container" id="analytics-traffic-container"> <div class="graph-container" id="analytics-traffic-container">
<i class="fa fa-expand"></i>
<canvas id="analytics-traffic" width="100%" height="400"></canvas> <canvas id="analytics-traffic" width="100%" height="400"></canvas>
</div> </div>
<hr/> <hr/>

Loading…
Cancel
Save