Add "today" period with hourly breakdown, and fix timezone issues

This fix ensures that before aggregating daily/hourly statistics, dates
are converted into the local timezone ("flarum-statistics.timezone" in
the settings table).
This commit is contained in:
Toby Zerner 2017-12-11 19:01:38 +10:30
parent 8860def12e
commit 6857d32d22
3 changed files with 106 additions and 41 deletions

View File

@ -36,14 +36,14 @@ System.register('flarum/statistics/components/StatisticsWidget', ['flarum/compon
value: function init() { value: function init() {
babelHelpers.get(StatisticsWidget.prototype.__proto__ || Object.getPrototypeOf(StatisticsWidget.prototype), 'init', this).call(this); babelHelpers.get(StatisticsWidget.prototype.__proto__ || Object.getPrototypeOf(StatisticsWidget.prototype), 'init', this).call(this);
var today = new Date(); var today = new Date().setHours(0, 0, 0, 0) / 1000;
today.setHours(0, 0, 0, 0);
this.entities = ['users', 'discussions', 'posts']; this.entities = ['users', 'discussions', 'posts'];
this.periods = { this.periods = {
last_7_days: { start: today - 86400000 * 6, end: today, step: 86400000 }, today: { start: today, end: today + 86400, step: 3600 },
last_28_days: { start: today - 86400000 * 27, end: today, step: 86400000 }, last_7_days: { start: today - 86400 * 7, end: today, step: 86400 },
last_12_months: { start: today - 86400000 * 364, end: today, step: 86400000 * 7 } last_28_days: { start: today - 86400 * 28, end: today, step: 86400 },
last_12_months: { start: today - 86400 * 364, end: today, step: 86400 * 7 }
}; };
this.selectedEntity = 'users'; this.selectedEntity = 'users';
@ -81,7 +81,10 @@ System.register('flarum/statistics/components/StatisticsWidget', ['flarum/compon
Object.keys(this.periods).map(function (period) { Object.keys(this.periods).map(function (period) {
return m( return m(
Button, Button,
{ active: period === _this2.selectedPeriod, onclick: _this2.changePeriod.bind(_this2, period) }, {
active: period === _this2.selectedPeriod,
onclick: _this2.changePeriod.bind(_this2, period),
icon: period === _this2.selectedPeriod ? 'check' : true },
app.translator.trans('flarum-statistics.admin.statistics.' + period + '_label') app.translator.trans('flarum-statistics.admin.statistics.' + period + '_label')
); );
}) })
@ -128,22 +131,37 @@ System.register('flarum/statistics/components/StatisticsWidget', ['flarum/compon
}, { }, {
key: 'drawChart', key: 'drawChart',
value: function drawChart(elm, isInitialized, context) { value: function drawChart(elm, isInitialized, context) {
var entity = this.selectedEntity; if (context.chart && context.entity === this.selectedEntity && context.period === this.selectedPeriod) {
return;
}
var period = this.periods[this.selectedPeriod]; var period = this.periods[this.selectedPeriod];
var periodLength = period.end - period.start;
var labels = []; var labels = [];
var thisPeriod = []; var thisPeriod = [];
var lastPeriod = []; var lastPeriod = [];
for (var i = period.start; i <= period.end; i += period.step) { for (var i = period.start; i < period.end; i += period.step) {
labels.push(moment(i).format('D MMM')); var label = void 0;
thisPeriod.push(this.getPeriodCount(entity, { start: i, end: i + period.step })); if (period.step < 86400) {
label = moment.unix(i).format('h A');
} else {
label = moment.unix(i).format('D MMM');
var periodLength = period.end - period.start; if (period.step > 86400) {
lastPeriod.push(this.getPeriodCount(entity, { start: i - periodLength, end: i - periodLength + period.step })); label += ' - ' + moment.unix(i + period.step - 1).format('D MMM');
}
}
labels.push(label);
thisPeriod.push(this.getPeriodCount(this.selectedEntity, { start: i, end: i + period.step }));
lastPeriod.push(this.getPeriodCount(this.selectedEntity, { start: i - periodLength, end: i - periodLength + period.step }));
} }
var datasets = [{ values: lastPeriod, title: 'Last period ➡' }, { values: thisPeriod, title: 'This period' }]; var datasets = [{ values: lastPeriod }, { values: thisPeriod }];
if (!context.chart) { if (!context.chart) {
context.chart = new Chart({ context.chart = new Chart({
@ -155,17 +173,14 @@ System.register('flarum/statistics/components/StatisticsWidget', ['flarum/compon
y_axis_mode: 'span', y_axis_mode: 'span',
is_series: 1, is_series: 1,
show_dots: 0, show_dots: 0,
colors: ['rgba(0,0,0,0.1)', app.forum.attribute('themePrimaryColor')], colors: ['rgba(0, 0, 0, 0.1)', app.forum.attribute('themePrimaryColor')]
format_tooltip_x: function format_tooltip_x(d) {
return d;
},
format_tooltip_y: function format_tooltip_y(d) {
return d;
}
}); });
} else {
context.chart.update_values(datasets, labels);
} }
context.chart.update_values(datasets, labels); context.entity = this.selectedEntity;
context.period = this.selectedPeriod;
} }
}, { }, {
key: 'changeEntity', key: 'changeEntity',
@ -189,9 +204,7 @@ System.register('flarum/statistics/components/StatisticsWidget', ['flarum/compon
var count = 0; var count = 0;
for (var day in daily) { for (var day in daily) {
var date = new Date(day); if (day >= period.start && day < period.end) {
if (date > period.start && date < period.end) {
count += daily[day]; count += daily[day];
} }
} }

View File

@ -19,14 +19,14 @@ export default class StatisticsWidget extends DashboardWidget {
init() { init() {
super.init(); super.init();
const today = new Date(); const today = new Date().setHours(0, 0, 0, 0) / 1000;
today.setHours(0, 0, 0, 0);
this.entities = ['users', 'discussions', 'posts']; this.entities = ['users', 'discussions', 'posts'];
this.periods = { this.periods = {
last_7_days: {start: today - 86400000 * 6, end: today, step: 86400000}, today: {start: today, end: today + 86400, step: 3600},
last_28_days: {start: today - 86400000 * 27, end: today, step: 86400000}, last_7_days: {start: today - 86400 * 7, end: today, step: 86400},
last_12_months: {start: today - 86400000 * 364, end: today, step: 86400000 * 7} last_28_days: {start: today - 86400 * 28, end: today, step: 86400},
last_12_months: {start: today - 86400 * 364, end: today, step: 86400 * 7}
}; };
this.selectedEntity = 'users'; this.selectedEntity = 'users';
@ -91,19 +91,30 @@ export default class StatisticsWidget extends DashboardWidget {
return; return;
} }
const entity = this.selectedEntity;
const period = this.periods[this.selectedPeriod]; const period = this.periods[this.selectedPeriod];
const periodLength = period.end - period.start;
const labels = []; const labels = [];
const thisPeriod = []; const thisPeriod = [];
const lastPeriod = []; const lastPeriod = [];
for (let i = period.start; i <= period.end; i += period.step) { for (let i = period.start; i < period.end; i += period.step) {
labels.push(moment(i).format('D MMM')); let label;
thisPeriod.push(this.getPeriodCount(entity, {start: i, end: i + period.step})); if (period.step < 86400) {
label = moment.unix(i).format('h A');
} else {
label = moment.unix(i).format('D MMM');
const periodLength = period.end - period.start; if (period.step > 86400) {
lastPeriod.push(this.getPeriodCount(entity, {start: i - periodLength, end: i - periodLength + period.step})); label += ' - ' + moment.unix(i + period.step - 1).format('D MMM');
}
}
labels.push(label);
thisPeriod.push(this.getPeriodCount(this.selectedEntity, {start: i, end: i + period.step}));
lastPeriod.push(this.getPeriodCount(this.selectedEntity, {start: i - periodLength, end: i - periodLength + period.step}));
} }
const datasets = [ const datasets = [
@ -148,9 +159,7 @@ export default class StatisticsWidget extends DashboardWidget {
let count = 0; let count = 0;
for (const day in daily) { for (const day in daily) {
const date = new Date(day); if (day >= period.start && day < period.end) {
if (date > period.start && date < period.end) {
count += daily[day]; count += daily[day];
} }
} }

View File

@ -12,20 +12,41 @@
namespace Flarum\Statistics\Listener; namespace Flarum\Statistics\Listener;
use DateTime; use DateTime;
use DateTimeZone;
use Flarum\Core\Discussion; use Flarum\Core\Discussion;
use Flarum\Core\Post; use Flarum\Core\Post;
use Flarum\Core\User; use Flarum\Core\User;
use Flarum\Event\ConfigureWebApp; use Flarum\Event\ConfigureWebApp;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Events\Dispatcher;
class AddStatisticsData class AddStatisticsData
{ {
/**
* @var SettingsRepositoryInterface
*/
protected $settings;
/**
* @param SettingsRepositoryInterface $settings
*/
public function __construct(SettingsRepositoryInterface $settings)
{
$this->settings = $settings;
}
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events) public function subscribe(Dispatcher $events)
{ {
$events->listen(ConfigureWebApp::class, [$this, 'addStatisticsData']); $events->listen(ConfigureWebApp::class, [$this, 'addStatisticsData']);
} }
/**
* @param ConfigureWebApp $event
*/
public function addStatisticsData(ConfigureWebApp $event) public function addStatisticsData(ConfigureWebApp $event)
{ {
$event->view->setVariable('statistics', $this->getStatistics()); $event->view->setVariable('statistics', $this->getStatistics());
@ -49,11 +70,33 @@ class AddStatisticsData
private function getDailyCounts(Builder $query, $column) private function getDailyCounts(Builder $query, $column)
{ {
// Calculate the offset between the server timezone (which is used for
// dates stored in the database) and the user's timezone (set via the
// settings table). We will use this to adjust dates before aggregating
// daily/hourly statistics.
$offset = $this->getTimezoneOffset();
return $query return $query
->selectRaw('DATE('.$column.') as date') ->selectRaw(
'UNIX_TIMESTAMP(
DATE_FORMAT(
@date := DATE_ADD('.$column.', INTERVAL ? SECOND), -- correct for timezone
IF(@date > ?, \'%Y-%m-%d %H:00:00\', \'%Y-%m-%d\') -- if within the last 48 hours, group by hour
)
) as period',
[$offset, new DateTime('-48 hours')]
)
->selectRaw('COUNT(id) as count') ->selectRaw('COUNT(id) as count')
->where($column, '>', new DateTime('-24 months')) ->where($column, '>', new DateTime('-24 months'))
->groupBy('date') ->groupBy('period')
->lists('count', 'date'); ->lists('count', 'period');
}
private function getTimezoneOffset()
{
$dataTimezone = new DateTimeZone(date_default_timezone_get());
$displayTimezone = new DateTimeZone($this->settings->get('flarum-statistics.timezone', date_default_timezone_get()));
return $displayTimezone->getOffset(new DateTime('now', $dataTimezone));
} }
} }