Add date range to statistics
This commit is contained in:
parent
420b4f2a85
commit
b34fbb1ee7
|
@ -1,5 +1,7 @@
|
|||
const router = require('express').Router();
|
||||
const mongoose = require('mongoose');
|
||||
const { DateTime } = require('luxon');
|
||||
|
||||
const Track = mongoose.model('Track');
|
||||
const User = mongoose.model('User');
|
||||
const wrapRoute = require('../../_helpers/wrapRoute');
|
||||
|
@ -13,31 +15,50 @@ const TRACK_DURATION_ROUNDING = 120;
|
|||
router.get(
|
||||
'/',
|
||||
wrapRoute(async (req, res) => {
|
||||
const trackCount = await Track.find().count();
|
||||
const publicTrackCount = await Track.find({ public: true }).count();
|
||||
const userCount = await User.find().count();
|
||||
const start = DateTime.fromISO(req.query.start);
|
||||
const end = DateTime.fromISO(req.query.end);
|
||||
|
||||
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([
|
||||
{
|
||||
$match: {
|
||||
'statistics.recordedAt': dateFilter,
|
||||
},
|
||||
},
|
||||
{
|
||||
$addFields: {
|
||||
trackLength: {
|
||||
$cond: [
|
||||
{$lt: ['$statistics.length', 500000]},
|
||||
'$statistics.length',
|
||||
0,
|
||||
],
|
||||
$cond: [{ $lt: ['$statistics.length', 500000] }, '$statistics.length', 0],
|
||||
},
|
||||
numEvents: '$statistics.numEvents',
|
||||
trackDuration: {
|
||||
$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'] },
|
||||
0,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{ $project: {trackLength: true, numEvents: true, trackDuration: true } },
|
||||
{ $project: { trackLength: true, numEvents: true, trackDuration: true } },
|
||||
{
|
||||
$group: {
|
||||
_id: 'sum',
|
||||
|
@ -48,12 +69,14 @@ router.get(
|
|||
},
|
||||
]);
|
||||
|
||||
const [trackLength, numEvents, trackDuration] = trackStats.length > 0
|
||||
? [trackStats[0].trackLength, trackStats[0].numEvents, trackStats[0].trackDuration]
|
||||
: [0,0,0];
|
||||
const [trackLength, numEvents, trackDuration] =
|
||||
trackStats.length > 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 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({
|
||||
publicTrackCount,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import React from 'react'
|
||||
import React, {useState, useCallback} from 'react'
|
||||
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 {of, from} from 'rxjs'
|
||||
import {map, switchMap} from 'rxjs/operators'
|
||||
import {of, from, concat} from 'rxjs'
|
||||
import {tap, map, switchMap, distinctUntilChanged} from 'rxjs/operators'
|
||||
import {fromLonLat} from 'ol/proj'
|
||||
import {Duration} from 'luxon'
|
||||
import {Duration, DateTime} from 'luxon'
|
||||
|
||||
import api from '../api'
|
||||
import {Map, Page, RoadsLayer} from '../components'
|
||||
|
@ -16,7 +16,7 @@ import styles from './HomePage.module.scss'
|
|||
function formatDuration(seconds) {
|
||||
return Duration.fromMillis((seconds ?? 0) * 1000)
|
||||
.as('hours')
|
||||
.toFixed(1)
|
||||
.toFixed(1) + ' h'
|
||||
}
|
||||
|
||||
function WelcomeMap() {
|
||||
|
@ -36,34 +36,79 @@ function WelcomeMap() {
|
|||
}
|
||||
|
||||
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 (
|
||||
<>
|
||||
<Header as="h2">Statistics</Header>
|
||||
|
||||
<Segment>
|
||||
<Loader active={stats == null} />
|
||||
<div>
|
||||
<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">
|
||||
<Statistic>
|
||||
<Statistic.Value>{Number(stats?.trackLength / 1000).toFixed(1)} km</Statistic.Value>
|
||||
<Statistic.Label>Total track length</Statistic.Label>
|
||||
</Statistic>
|
||||
<Statistic>
|
||||
<Statistic.Value>{formatDuration(stats?.trackDuration)} h</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>
|
||||
<Menu widths={3} attached="bottom" size="small">
|
||||
<Menu.Item name="this_month" active={timeframe === 'this_month'} onClick={onClick}>
|
||||
This month
|
||||
</Menu.Item>
|
||||
<Menu.Item name="this_year" active={timeframe === 'this_year'} onClick={onClick}>
|
||||
This year
|
||||
</Menu.Item>
|
||||
<Menu.Item name="all_time" active={timeframe === 'all_time'} onClick={onClick}>
|
||||
All time
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue