Add date range to statistics
This commit is contained in:
parent
420b4f2a85
commit
b34fbb1ee7
|
@ -1,5 +1,7 @@
|
||||||
const router = require('express').Router();
|
const router = require('express').Router();
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
|
const { DateTime } = require('luxon');
|
||||||
|
|
||||||
const Track = mongoose.model('Track');
|
const Track = mongoose.model('Track');
|
||||||
const User = mongoose.model('User');
|
const User = mongoose.model('User');
|
||||||
const wrapRoute = require('../../_helpers/wrapRoute');
|
const wrapRoute = require('../../_helpers/wrapRoute');
|
||||||
|
@ -13,31 +15,50 @@ const TRACK_DURATION_ROUNDING = 120;
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
wrapRoute(async (req, res) => {
|
wrapRoute(async (req, res) => {
|
||||||
const trackCount = await Track.find().count();
|
const start = DateTime.fromISO(req.query.start);
|
||||||
const publicTrackCount = await Track.find({ public: true }).count();
|
const end = DateTime.fromISO(req.query.end);
|
||||||
const userCount = await User.find().count();
|
|
||||||
|
const dateFilter = {
|
||||||
|
$ne: null,
|
||||||
|
...(start.isValid ? { $gte: start.toJSDate() } : {}),
|
||||||
|
...(end.isValid ? { $lt: end.toJSDate() } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const trackCount = await Track.find({
|
||||||
|
'statistics.recordedAt': dateFilter,
|
||||||
|
}).count();
|
||||||
|
|
||||||
|
const publicTrackCount = await Track.find({
|
||||||
|
'statistics.recordedAt': dateFilter,
|
||||||
|
public: true,
|
||||||
|
}).count();
|
||||||
|
|
||||||
|
const userCount = await User.find({
|
||||||
|
createdAt: dateFilter,
|
||||||
|
}).count();
|
||||||
|
|
||||||
const trackStats = await Track.aggregate([
|
const trackStats = await Track.aggregate([
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
'statistics.recordedAt': dateFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
$addFields: {
|
$addFields: {
|
||||||
trackLength: {
|
trackLength: {
|
||||||
$cond: [
|
$cond: [{ $lt: ['$statistics.length', 500000] }, '$statistics.length', 0],
|
||||||
{$lt: ['$statistics.length', 500000]},
|
|
||||||
'$statistics.length',
|
|
||||||
0,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
numEvents: '$statistics.numEvents',
|
numEvents: '$statistics.numEvents',
|
||||||
trackDuration: {
|
trackDuration: {
|
||||||
$cond: [
|
$cond: [
|
||||||
{ $and: ['$statistics.recordedUntil', '$statistics.recordedAt', {$gt: ['$statistics.recordedAt', new Date('2010-01-01')]}] },
|
{ $and: ['$statistics.recordedUntil', { $gt: ['$statistics.recordedAt', new Date('2010-01-01')] }] },
|
||||||
{ $subtract: ['$statistics.recordedUntil', '$statistics.recordedAt'] },
|
{ $subtract: ['$statistics.recordedUntil', '$statistics.recordedAt'] },
|
||||||
0,
|
0,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ $project: {trackLength: true, numEvents: true, trackDuration: true } },
|
{ $project: { trackLength: true, numEvents: true, trackDuration: true } },
|
||||||
{
|
{
|
||||||
$group: {
|
$group: {
|
||||||
_id: 'sum',
|
_id: 'sum',
|
||||||
|
@ -48,12 +69,14 @@ router.get(
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [trackLength, numEvents, trackDuration] = trackStats.length > 0
|
const [trackLength, numEvents, trackDuration] =
|
||||||
? [trackStats[0].trackLength, trackStats[0].numEvents, trackStats[0].trackDuration]
|
trackStats.length > 0
|
||||||
: [0,0,0];
|
? [trackStats[0].trackLength, trackStats[0].numEvents, trackStats[0].trackDuration]
|
||||||
|
: [0, 0, 0];
|
||||||
|
|
||||||
const trackLengthPrivatized = Math.floor(trackLength / TRACK_LENGTH_ROUNDING) * TRACK_LENGTH_ROUNDING;
|
const trackLengthPrivatized = Math.floor(trackLength / TRACK_LENGTH_ROUNDING) * TRACK_LENGTH_ROUNDING;
|
||||||
const trackDurationPrivatized = Math.round(trackDuration / 1000 / TRACK_DURATION_ROUNDING) * TRACK_DURATION_ROUNDING;
|
const trackDurationPrivatized =
|
||||||
|
Math.round(trackDuration / 1000 / TRACK_DURATION_ROUNDING) * TRACK_DURATION_ROUNDING;
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
publicTrackCount,
|
publicTrackCount,
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React from 'react'
|
import React, {useState, useCallback} from 'react'
|
||||||
import {Link} from 'react-router-dom'
|
import {Link} from 'react-router-dom'
|
||||||
import {Message, Grid, Loader, Statistic, Segment, Header, Item} from 'semantic-ui-react'
|
import {Message, Grid, Loader, Statistic, Segment, Header, Item, Menu} from 'semantic-ui-react'
|
||||||
import {useObservable} from 'rxjs-hooks'
|
import {useObservable} from 'rxjs-hooks'
|
||||||
import {of, from} from 'rxjs'
|
import {of, from, concat} from 'rxjs'
|
||||||
import {map, switchMap} from 'rxjs/operators'
|
import {tap, map, switchMap, distinctUntilChanged} from 'rxjs/operators'
|
||||||
import {fromLonLat} from 'ol/proj'
|
import {fromLonLat} from 'ol/proj'
|
||||||
import {Duration} from 'luxon'
|
import {Duration, DateTime} from 'luxon'
|
||||||
|
|
||||||
import api from '../api'
|
import api from '../api'
|
||||||
import {Map, Page, RoadsLayer} from '../components'
|
import {Map, Page, RoadsLayer} from '../components'
|
||||||
|
@ -16,7 +16,7 @@ import styles from './HomePage.module.scss'
|
||||||
function formatDuration(seconds) {
|
function formatDuration(seconds) {
|
||||||
return Duration.fromMillis((seconds ?? 0) * 1000)
|
return Duration.fromMillis((seconds ?? 0) * 1000)
|
||||||
.as('hours')
|
.as('hours')
|
||||||
.toFixed(1)
|
.toFixed(1) + ' h'
|
||||||
}
|
}
|
||||||
|
|
||||||
function WelcomeMap() {
|
function WelcomeMap() {
|
||||||
|
@ -36,34 +36,79 @@ function WelcomeMap() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Stats() {
|
function Stats() {
|
||||||
const stats = useObservable(() => of(null).pipe(switchMap(() => api.fetch('/stats'))))
|
const [timeframe, setTimeframe] = useState('all_time')
|
||||||
|
const onClick = useCallback((_e, {name}) => setTimeframe(name), [setTimeframe])
|
||||||
|
|
||||||
|
const stats = useObservable(
|
||||||
|
(_$, inputs$) =>
|
||||||
|
inputs$.pipe(
|
||||||
|
map((inputs) => inputs[0]),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
map((timeframe_) => {
|
||||||
|
const now = DateTime.now()
|
||||||
|
|
||||||
|
switch (timeframe_) {
|
||||||
|
case 'this_month':
|
||||||
|
return {
|
||||||
|
start: now.startOf('month').toISODate(),
|
||||||
|
end: now.endOf('month').toISODate(),
|
||||||
|
}
|
||||||
|
case 'this_year':
|
||||||
|
return {
|
||||||
|
start: now.startOf('year').toISODate(),
|
||||||
|
end: now.endOf('year').toISODate(),
|
||||||
|
}
|
||||||
|
case 'all_time':
|
||||||
|
default:
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
switchMap((query) => concat(of(null), from(api.get('/stats', {query})))),
|
||||||
|
tap(console.log),
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
[timeframe]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header as="h2">Statistics</Header>
|
<Header as="h2">Statistics</Header>
|
||||||
|
|
||||||
<Segment>
|
<div>
|
||||||
<Loader active={stats == null} />
|
<Segment attached="top">
|
||||||
|
<Loader active={stats == null} />
|
||||||
|
<Statistic.Group widths={2} size="tiny">
|
||||||
|
<Statistic>
|
||||||
|
<Statistic.Value>{stats ? `${Number(stats?.trackLength / 1000).toFixed(1)} km` : '...'}</Statistic.Value>
|
||||||
|
<Statistic.Label>Total track length</Statistic.Label>
|
||||||
|
</Statistic>
|
||||||
|
<Statistic>
|
||||||
|
<Statistic.Value>{stats ? formatDuration(stats?.trackDuration) : '...'}</Statistic.Value>
|
||||||
|
<Statistic.Label>Time recorded</Statistic.Label>
|
||||||
|
</Statistic>
|
||||||
|
<Statistic>
|
||||||
|
<Statistic.Value>{stats?.numEvents ?? '...'}</Statistic.Value>
|
||||||
|
<Statistic.Label>Events confirmed</Statistic.Label>
|
||||||
|
</Statistic>
|
||||||
|
<Statistic>
|
||||||
|
<Statistic.Value>{stats?.userCount ?? '...'}</Statistic.Value>
|
||||||
|
<Statistic.Label>Members joined</Statistic.Label>
|
||||||
|
</Statistic>
|
||||||
|
</Statistic.Group>
|
||||||
|
</Segment>
|
||||||
|
|
||||||
<Statistic.Group widths={2} size="tiny">
|
<Menu widths={3} attached="bottom" size="small">
|
||||||
<Statistic>
|
<Menu.Item name="this_month" active={timeframe === 'this_month'} onClick={onClick}>
|
||||||
<Statistic.Value>{Number(stats?.trackLength / 1000).toFixed(1)} km</Statistic.Value>
|
This month
|
||||||
<Statistic.Label>Total track length</Statistic.Label>
|
</Menu.Item>
|
||||||
</Statistic>
|
<Menu.Item name="this_year" active={timeframe === 'this_year'} onClick={onClick}>
|
||||||
<Statistic>
|
This year
|
||||||
<Statistic.Value>{formatDuration(stats?.trackDuration)} h</Statistic.Value>
|
</Menu.Item>
|
||||||
<Statistic.Label>Time recorded</Statistic.Label>
|
<Menu.Item name="all_time" active={timeframe === 'all_time'} onClick={onClick}>
|
||||||
</Statistic>
|
All time
|
||||||
<Statistic>
|
</Menu.Item>
|
||||||
<Statistic.Value>{stats?.numEvents}</Statistic.Value>
|
</Menu>
|
||||||
<Statistic.Label>Events confirmed</Statistic.Label>
|
</div>
|
||||||
</Statistic>
|
|
||||||
<Statistic>
|
|
||||||
<Statistic.Value>{stats?.userCount}</Statistic.Value>
|
|
||||||
<Statistic.Label>Members joined</Statistic.Label>
|
|
||||||
</Statistic>
|
|
||||||
</Statistic.Group>
|
|
||||||
</Segment>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue