From b34fbb1ee74f7c6ddfb2a48f25133c3e5e236e0c Mon Sep 17 00:00:00 2001 From: Paul Bienkowski Date: Fri, 14 May 2021 20:16:05 +0200 Subject: [PATCH] Add date range to statistics --- api/src/routes/api/stats.js | 51 ++++++++++++----- frontend/src/pages/HomePage.js | 101 ++++++++++++++++++++++++--------- 2 files changed, 110 insertions(+), 42 deletions(-) diff --git a/api/src/routes/api/stats.js b/api/src/routes/api/stats.js index 868f97c..a1c8934 100644 --- a/api/src/routes/api/stats.js +++ b/api/src/routes/api/stats.js @@ -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, diff --git a/frontend/src/pages/HomePage.js b/frontend/src/pages/HomePage.js index 681ab0a..76e94af 100644 --- a/frontend/src/pages/HomePage.js +++ b/frontend/src/pages/HomePage.js @@ -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 ( <>
Statistics
- - +
+ + + + + {stats ? `${Number(stats?.trackLength / 1000).toFixed(1)} km` : '...'} + Total track length + + + {stats ? formatDuration(stats?.trackDuration) : '...'} + Time recorded + + + {stats?.numEvents ?? '...'} + Events confirmed + + + {stats?.userCount ?? '...'} + Members joined + + + - - - {Number(stats?.trackLength / 1000).toFixed(1)} km - Total track length - - - {formatDuration(stats?.trackDuration)} h - Time recorded - - - {stats?.numEvents} - Events confirmed - - - {stats?.userCount} - Members joined - - - + + + This month + + + This year + + + All time + + +
) }