From 859f690ba4703f55216c60b8cbf0f69c6c0a847b Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 11 Jun 2018 18:47:04 +0200 Subject: [PATCH] API: timezone support for statistics - fix #11 + Client fix --- .../fittrackee_api/activities/stats.py | 16 +- .../fittrackee_api/tests/test_stats_api.py | 184 +++++++++++++++++- .../src/components/Dashboard/Statistics.jsx | 6 +- fittrackee_client/src/utils.js | 2 +- 4 files changed, 197 insertions(+), 11 deletions(-) diff --git a/fittrackee_api/fittrackee_api/activities/stats.py b/fittrackee_api/fittrackee_api/activities/stats.py index 569a37f96..0a7a88dc5 100644 --- a/fittrackee_api/fittrackee_api/activities/stats.py +++ b/fittrackee_api/fittrackee_api/activities/stats.py @@ -6,6 +6,7 @@ from ..users.models import User from ..users.utils import authenticate from .models import Activity, Sport +from .utils import get_datetime_with_tz from .utils_format import convert_timedelta_to_integer stats_blueprint = Blueprint('stats', __name__) @@ -23,7 +24,14 @@ def get_activities(user_id, type): params = request.args.copy() date_from = params.get('from') + if date_from: + date_from = datetime.strptime(date_from, '%Y-%m-%d') + _, date_from = get_datetime_with_tz(user_id, date_from) date_to = params.get('to') + if date_to: + date_to = datetime.strptime(f'{date_to} 23:59:59', + '%Y-%m-%d %H:%M:%S') + _, date_to = get_datetime_with_tz(user_id, date_to) sport_id = params.get('sport_id') time = params.get('time') @@ -41,11 +49,9 @@ def get_activities(user_id, type): activities = Activity.query.filter( Activity.user_id == user_id, - Activity.activity_date >= datetime.strptime(date_from, '%Y-%m-%d') - if date_from else True, - Activity.activity_date < ( - datetime.strptime(date_to, '%Y-%m-%d') + timedelta(days=1) - ) if date_to else True, + Activity.activity_date >= date_from if date_from else True, + Activity.activity_date < date_to + timedelta(seconds=1) + if date_to else True, Activity.sport_id == sport_id if sport_id else True, ).order_by( Activity.activity_date.asc() diff --git a/fittrackee_api/fittrackee_api/tests/test_stats_api.py b/fittrackee_api/fittrackee_api/tests/test_stats_api.py index 237958630..025cac08e 100644 --- a/fittrackee_api/fittrackee_api/tests/test_stats_api.py +++ b/fittrackee_api/fittrackee_api/tests/test_stats_api.py @@ -207,6 +207,51 @@ def test_get_stats_by_time_all_activities_april_2018( } +def test_get_stats_by_time_all_activities_april_2018_paris( + app, user_1_paris, sport_1_cycling, sport_2_running, + seven_activities_user_1, activity_running_user_1 +): + client = app.test_client() + resp_login = client.post( + '/api/auth/login', + data=json.dumps(dict( + email='test@test.com', + password='12345678' + )), + content_type='application/json' + ) + response = client.get( + f'/api/stats/{user_1_paris.id}/by_time?from=2018-04-01&to=2018-04-30', + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 200 + assert 'success' in data['status'] + assert data['data']['statistics'] == \ + { + '2018': + { + '1': + { + 'nb_activities': 1, + 'total_distance': 8.0, + 'total_duration': 6000 + }, + '2': + { + 'nb_activities': 1, + 'total_distance': 12.0, + 'total_duration': 6000 + } + } + } + + def test_get_stats_by_year_all_activities( app, user_1, sport_1_cycling, sport_2_running, seven_activities_user_1, activity_running_user_1 @@ -306,6 +351,51 @@ def test_get_stats_by_year_all_activities_april_2018( } +def test_get_stats_by_year_all_activities_april_2018_paris( + app, user_1_paris, sport_1_cycling, sport_2_running, + seven_activities_user_1, activity_running_user_1 +): + client = app.test_client() + resp_login = client.post( + '/api/auth/login', + data=json.dumps(dict( + email='test@test.com', + password='12345678' + )), + content_type='application/json' + ) + response = client.get( + f'/api/stats/{user_1_paris.id}/by_time?from=2018-04-01&to=2018-04-30&time=year', # noqa + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 200 + assert 'success' in data['status'] + assert data['data']['statistics'] == \ + { + '2018': + { + '1': + { + 'nb_activities': 1, + 'total_distance': 8.0, + 'total_duration': 6000 + }, + '2': + { + 'nb_activities': 1, + 'total_distance': 12.0, + 'total_duration': 6000 + } + } + } + + def test_get_stats_by_month_all_activities( app, user_1, sport_1_cycling, sport_2_running, seven_activities_user_1, activity_running_user_1 @@ -396,6 +486,96 @@ def test_get_stats_by_month_all_activities( } +def test_get_stats_by_month_all_activities_new_york( + app, user_1_full, sport_1_cycling, sport_2_running, + seven_activities_user_1, activity_running_user_1 +): + client = app.test_client() + resp_login = client.post( + '/api/auth/login', + data=json.dumps(dict( + email='test@test.com', + password='12345678' + )), + content_type='application/json' + ) + response = client.get( + f'/api/stats/{user_1_full.id}/by_time?time=month', + headers=dict( + Authorization='Bearer ' + json.loads( + resp_login.data.decode() + )['auth_token'] + ) + ) + data = json.loads(response.data.decode()) + + assert response.status_code == 200 + assert 'success' in data['status'] + assert data['data']['statistics'] == \ + { + '2017-03': + { + '1': + { + 'nb_activities': 1, + 'total_distance': 5.0, + 'total_duration': 1024 + } + }, + '2017-06': + { + '1': + { + 'nb_activities': 1, + 'total_distance': 10.0, + 'total_duration': 3456 + } + }, + '2018-01': + { + '1': + { + 'nb_activities': 1, + 'total_distance': 10.0, + 'total_duration': 1024 + } + }, + '2018-02': + { + '1': + { + 'nb_activities': 2, + 'total_distance': 11.0, + 'total_duration': 1600 + } + }, + '2018-04': + { + '1': + { + 'nb_activities': 1, + 'total_distance': 8.0, + 'total_duration': 6000 + }, + '2': + { + 'nb_activities': 1, + 'total_distance': 12.0, + 'total_duration': 6000 + } + }, + '2018-05': + { + '1': + { + 'nb_activities': 1, + 'total_distance': 10.0, + 'total_duration': 3000 + } + } + } + + def test_get_stats_by_month_all_activities_april_2018( app, user_1, sport_1_cycling, sport_2_running, seven_activities_user_1, activity_running_user_1 @@ -442,7 +622,7 @@ def test_get_stats_by_month_all_activities_april_2018( def test_get_stats_by_week_all_activities( - app, user_1, sport_1_cycling, sport_2_running, + app, user_1_full, sport_1_cycling, sport_2_running, seven_activities_user_1, activity_running_user_1 ): client = app.test_client() @@ -455,7 +635,7 @@ def test_get_stats_by_week_all_activities( content_type='application/json' ) response = client.get( - f'/api/stats/{user_1.id}/by_time?time=week', + f'/api/stats/{user_1_full.id}/by_time?time=week', headers=dict( Authorization='Bearer ' + json.loads( resp_login.data.decode() diff --git a/fittrackee_client/src/components/Dashboard/Statistics.jsx b/fittrackee_client/src/components/Dashboard/Statistics.jsx index 56e7b453b..3d1016aa5 100644 --- a/fittrackee_client/src/components/Dashboard/Statistics.jsx +++ b/fittrackee_client/src/components/Dashboard/Statistics.jsx @@ -6,7 +6,7 @@ import { } from 'recharts' import { getStats } from '../../actions/stats' -import { activityColors, formatStats } from '../../utils' +import { activityColors, formatDuration, formatStats } from '../../utils' class Statistics extends React.Component { @@ -90,7 +90,7 @@ class Statistics extends React.Component { tickFormatter={value => displayedData === 'distance' ? `${value} km` : displayedData === 'duration' - ? format(new Date(value * 1000), 'HH:mm') + ? format(formatDuration(value), 'HH:mm') : value } /> @@ -100,7 +100,7 @@ class Statistics extends React.Component { key={s.id} dataKey={s.label} formatter={value => displayedData === 'duration' - ? format(new Date(value * 1000), 'HH:mm') + ? format(formatDuration(value), 'HH:mm') : value } stackId="a" diff --git a/fittrackee_client/src/utils.js b/fittrackee_client/src/utils.js index 4f0979db2..090962798 100644 --- a/fittrackee_client/src/utils.js +++ b/fittrackee_client/src/utils.js @@ -117,7 +117,7 @@ export const formatRecord = (record, tz) => { } } -const formatDuration = seconds => { +export const formatDuration = seconds => { let newDate = new Date(0) newDate = subHours(newDate.setSeconds(seconds), 1) return newDate.getTime()