API: timezone support for statistics - fix #11

+ Client fix
This commit is contained in:
Sam 2018-06-11 18:47:04 +02:00
parent 30a16f64b0
commit 859f690ba4
4 changed files with 197 additions and 11 deletions

View File

@ -6,6 +6,7 @@ from flask import Blueprint, jsonify, request
from ..users.models import User from ..users.models import User
from ..users.utils import authenticate from ..users.utils import authenticate
from .models import Activity, Sport from .models import Activity, Sport
from .utils import get_datetime_with_tz
from .utils_format import convert_timedelta_to_integer from .utils_format import convert_timedelta_to_integer
stats_blueprint = Blueprint('stats', __name__) stats_blueprint = Blueprint('stats', __name__)
@ -23,7 +24,14 @@ def get_activities(user_id, type):
params = request.args.copy() params = request.args.copy()
date_from = params.get('from') 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') 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') sport_id = params.get('sport_id')
time = params.get('time') time = params.get('time')
@ -41,11 +49,9 @@ def get_activities(user_id, type):
activities = Activity.query.filter( activities = Activity.query.filter(
Activity.user_id == user_id, Activity.user_id == user_id,
Activity.activity_date >= datetime.strptime(date_from, '%Y-%m-%d') Activity.activity_date >= date_from if date_from else True,
if date_from else True, Activity.activity_date < date_to + timedelta(seconds=1)
Activity.activity_date < ( if date_to else True,
datetime.strptime(date_to, '%Y-%m-%d') + timedelta(days=1)
) if date_to else True,
Activity.sport_id == sport_id if sport_id else True, Activity.sport_id == sport_id if sport_id else True,
).order_by( ).order_by(
Activity.activity_date.asc() Activity.activity_date.asc()

View File

@ -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( def test_get_stats_by_year_all_activities(
app, user_1, sport_1_cycling, sport_2_running, app, user_1, sport_1_cycling, sport_2_running,
seven_activities_user_1, activity_running_user_1 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( def test_get_stats_by_month_all_activities(
app, user_1, sport_1_cycling, sport_2_running, app, user_1, sport_1_cycling, sport_2_running,
seven_activities_user_1, activity_running_user_1 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( def test_get_stats_by_month_all_activities_april_2018(
app, user_1, sport_1_cycling, sport_2_running, app, user_1, sport_1_cycling, sport_2_running,
seven_activities_user_1, activity_running_user_1 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( 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 seven_activities_user_1, activity_running_user_1
): ):
client = app.test_client() client = app.test_client()
@ -455,7 +635,7 @@ def test_get_stats_by_week_all_activities(
content_type='application/json' content_type='application/json'
) )
response = client.get( 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( headers=dict(
Authorization='Bearer ' + json.loads( Authorization='Bearer ' + json.loads(
resp_login.data.decode() resp_login.data.decode()

View File

@ -6,7 +6,7 @@ import {
} from 'recharts' } from 'recharts'
import { getStats } from '../../actions/stats' import { getStats } from '../../actions/stats'
import { activityColors, formatStats } from '../../utils' import { activityColors, formatDuration, formatStats } from '../../utils'
class Statistics extends React.Component { class Statistics extends React.Component {
@ -90,7 +90,7 @@ class Statistics extends React.Component {
tickFormatter={value => displayedData === 'distance' tickFormatter={value => displayedData === 'distance'
? `${value} km` ? `${value} km`
: displayedData === 'duration' : displayedData === 'duration'
? format(new Date(value * 1000), 'HH:mm') ? format(formatDuration(value), 'HH:mm')
: value : value
} }
/> />
@ -100,7 +100,7 @@ class Statistics extends React.Component {
key={s.id} key={s.id}
dataKey={s.label} dataKey={s.label}
formatter={value => displayedData === 'duration' formatter={value => displayedData === 'duration'
? format(new Date(value * 1000), 'HH:mm') ? format(formatDuration(value), 'HH:mm')
: value : value
} }
stackId="a" stackId="a"

View File

@ -117,7 +117,7 @@ export const formatRecord = (record, tz) => {
} }
} }
const formatDuration = seconds => { export const formatDuration = seconds => {
let newDate = new Date(0) let newDate = new Date(0)
newDate = subHours(newDate.setSeconds(seconds), 1) newDate = subHours(newDate.setSeconds(seconds), 1)
return newDate.getTime() return newDate.getTime()