Merge branch 'dev' into ascent_in_ft
This commit is contained in:
@@ -95,4 +95,4 @@ jobs:
|
|||||||
export TEST_APP_URL=http://$(hostname --ip-address):5000
|
export TEST_APP_URL=http://$(hostname --ip-address):5000
|
||||||
sleep 5
|
sleep 5
|
||||||
nohup flask worker --processes=1 >> nohup.out 2>&1 &
|
nohup flask worker --processes=1 >> nohup.out 2>&1 &
|
||||||
pytest e2e --driver Remote --capability browserName firefox --host selenium --port 4444
|
pytest e2e --driver Remote --capability browserName firefox --selenium-host selenium --selenium-port 4444
|
||||||
|
|||||||
@@ -50,11 +50,18 @@ docker-build-all: docker-build docker-build-client
|
|||||||
docker-build-client:
|
docker-build-client:
|
||||||
docker-compose -f docker-compose-dev.yml build fittrackee_client
|
docker-compose -f docker-compose-dev.yml build fittrackee_client
|
||||||
|
|
||||||
docker-init: docker-init-db docker-restart docker-run-workers
|
docker-init: docker-run docker-init-db docker-restart docker-run-workers
|
||||||
|
|
||||||
docker-init-db:
|
docker-init-db:
|
||||||
docker-compose -f docker-compose-dev.yml exec fittrackee docker/init-database.sh
|
docker-compose -f docker-compose-dev.yml exec fittrackee docker/init-database.sh
|
||||||
|
|
||||||
|
docker-lint-client:
|
||||||
|
docker-compose -f docker-compose-dev.yml up -d fittrackee_client
|
||||||
|
docker-compose -f docker-compose-dev.yml exec fittrackee_client yarn lint
|
||||||
|
|
||||||
|
docker-lint-python: docker-run
|
||||||
|
docker-compose -f docker-compose-dev.yml exec fittrackee docker/lint-python.sh
|
||||||
|
|
||||||
docker-logs:
|
docker-logs:
|
||||||
docker-compose -f docker-compose-dev.yml logs --follow
|
docker-compose -f docker-compose-dev.yml logs --follow
|
||||||
|
|
||||||
@@ -85,6 +92,18 @@ docker-shell:
|
|||||||
docker-stop:
|
docker-stop:
|
||||||
docker-compose -f docker-compose-dev.yml stop
|
docker-compose -f docker-compose-dev.yml stop
|
||||||
|
|
||||||
|
docker-test-client:
|
||||||
|
docker-compose -f docker-compose-dev.yml up -d fittrackee_client
|
||||||
|
docker-compose -f docker-compose-dev.yml exec fittrackee_client yarn test:unit
|
||||||
|
|
||||||
|
# needs a running application
|
||||||
|
docker-test-e2e: docker-run
|
||||||
|
docker-compose -f docker-compose-dev.yml up -d selenium
|
||||||
|
docker-compose -f docker-compose-dev.yml exec fittrackee docker/test-e2e.sh $(PYTEST_ARGS)
|
||||||
|
|
||||||
|
docker-test-python: docker-run
|
||||||
|
docker-compose -f docker-compose-dev.yml exec fittrackee docker/test-python.sh $(PYTEST_ARGS)
|
||||||
|
|
||||||
docker-up:
|
docker-up:
|
||||||
docker-compose -f docker-compose-dev.yml up fittrackee
|
docker-compose -f docker-compose-dev.yml up fittrackee
|
||||||
|
|
||||||
@@ -184,11 +203,11 @@ set-admin:
|
|||||||
echo "Deprecated command, will be removed in a next version. Use 'user-set-admin' instead."
|
echo "Deprecated command, will be removed in a next version. Use 'user-set-admin' instead."
|
||||||
$(FTCLI) users update $(USERNAME) --set-admin true
|
$(FTCLI) users update $(USERNAME) --set-admin true
|
||||||
|
|
||||||
|
test-all: test-client test-python
|
||||||
|
|
||||||
test-e2e:
|
test-e2e:
|
||||||
$(PYTEST) e2e --driver firefox $(PYTEST_ARGS)
|
$(PYTEST) e2e --driver firefox $(PYTEST_ARGS)
|
||||||
|
|
||||||
test-all: test-client test-python
|
|
||||||
|
|
||||||
test-e2e-client:
|
test-e2e-client:
|
||||||
E2E_ARGS=client $(PYTEST) e2e --driver firefox $(PYTEST_ARGS)
|
E2E_ARGS=client $(PYTEST) e2e --driver firefox $(PYTEST_ARGS)
|
||||||
|
|
||||||
|
|||||||
+17
-4
@@ -12,6 +12,8 @@ services:
|
|||||||
- POSTGRES_PASSWORD=postgres
|
- POSTGRES_PASSWORD=postgres
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/db:/var/lib/postgresql/data
|
- ./data/db:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- fittrackee-net
|
||||||
|
|
||||||
fittrackee:
|
fittrackee:
|
||||||
container_name: fittrackee
|
container_name: fittrackee
|
||||||
@@ -24,14 +26,12 @@ services:
|
|||||||
- fittrackee-db
|
- fittrackee-db
|
||||||
- redis
|
- redis
|
||||||
- mail
|
- mail
|
||||||
links:
|
|
||||||
- fittrackee-db
|
|
||||||
- redis
|
|
||||||
- mail
|
|
||||||
volumes:
|
volumes:
|
||||||
- .:/usr/src/app
|
- .:/usr/src/app
|
||||||
- ./data/workouts:/usr/src/app/workouts
|
- ./data/workouts:/usr/src/app/workouts
|
||||||
- ./data/uploads:/usr/src/app/uploads
|
- ./data/uploads:/usr/src/app/uploads
|
||||||
|
networks:
|
||||||
|
- fittrackee-net
|
||||||
|
|
||||||
fittrackee_client:
|
fittrackee_client:
|
||||||
container_name: fittrackee_client
|
container_name: fittrackee_client
|
||||||
@@ -57,6 +57,8 @@ services:
|
|||||||
hostname: redis
|
hostname: redis
|
||||||
ports:
|
ports:
|
||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
|
networks:
|
||||||
|
- fittrackee-net
|
||||||
|
|
||||||
mail:
|
mail:
|
||||||
container_name: fittrackee-mailhog
|
container_name: fittrackee-mailhog
|
||||||
@@ -64,3 +66,14 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "1025:1025"
|
- "1025:1025"
|
||||||
- "8025:8025"
|
- "8025:8025"
|
||||||
|
networks:
|
||||||
|
- fittrackee-net
|
||||||
|
|
||||||
|
selenium:
|
||||||
|
image: selenium/standalone-firefox:latest
|
||||||
|
hostname: selenium
|
||||||
|
privileged: true
|
||||||
|
shm_size: 2g
|
||||||
|
|
||||||
|
networks:
|
||||||
|
fittrackee-net:
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
set -e
|
set -e
|
||||||
cd /usr/src/app
|
cd /usr/src/app
|
||||||
|
|
||||||
source .env.docker
|
source .env
|
||||||
|
|
||||||
ftcli db drop
|
ftcli db drop
|
||||||
ftcli db upgrade
|
ftcli db upgrade
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
cd /usr/src/app
|
||||||
|
|
||||||
|
source .env
|
||||||
|
|
||||||
|
mypy fittrackee
|
||||||
|
pytest --flake8 --isort --black -m "flake8 or isort or black" fittrackee e2e --ignore=fittrackee/migrations
|
||||||
@@ -2,6 +2,6 @@
|
|||||||
set -e
|
set -e
|
||||||
cd /usr/src/app
|
cd /usr/src/app
|
||||||
|
|
||||||
source .env.docker
|
source .env
|
||||||
|
|
||||||
flask worker --processes=$WORKERS_PROCESSES >> dramatiq.log 2>&1
|
flask worker --processes=$WORKERS_PROCESSES >> dramatiq.log 2>&1
|
||||||
|
|||||||
+1
-1
@@ -2,6 +2,6 @@
|
|||||||
set -e
|
set -e
|
||||||
cd /usr/src/app
|
cd /usr/src/app
|
||||||
|
|
||||||
source .env.docker
|
source .env
|
||||||
|
|
||||||
ftcli users update $1 --set-admin true
|
ftcli users update $1 --set-admin true
|
||||||
|
|||||||
+1
-1
@@ -2,6 +2,6 @@
|
|||||||
set -e
|
set -e
|
||||||
cd /usr/src/app
|
cd /usr/src/app
|
||||||
|
|
||||||
source .env.docker
|
source .env
|
||||||
|
|
||||||
/bin/bash
|
/bin/bash
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
cd /usr/src/app
|
||||||
|
|
||||||
|
source .env
|
||||||
|
|
||||||
|
export TEST_APP_URL=http://$(hostname --ip-address):5000
|
||||||
|
pytest e2e --driver Remote --capability browserName firefox --selenium-host selenium --selenium-port 4444 $*
|
||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
cd /usr/src/app
|
||||||
|
|
||||||
|
source .env
|
||||||
|
|
||||||
|
pytest fittrackee $*
|
||||||
@@ -45,6 +45,7 @@ Workouts
|
|||||||
- User records by sports:
|
- User records by sports:
|
||||||
- average speed
|
- average speed
|
||||||
- farthest distance
|
- farthest distance
|
||||||
|
- highest ascent (**new in 0.6.11**, can be hidden, see user preferences)
|
||||||
- longest duration
|
- longest duration
|
||||||
- maximum speed
|
- maximum speed
|
||||||
|
|
||||||
@@ -71,6 +72,7 @@ Account & preferences
|
|||||||
- A user can reset his password (*new in 0.3.0*)
|
- A user can reset his password (*new in 0.3.0*)
|
||||||
- A user can change his email address (*new in 0.6.0*)
|
- A user can change his email address (*new in 0.6.0*)
|
||||||
- A user can choose between metric system and imperial system for distance, elevation and speed display (*new in 0.5.0*)
|
- A user can choose between metric system and imperial system for distance, elevation and speed display (*new in 0.5.0*)
|
||||||
|
- A user can choose to display or hide ascent records and total on Dashboard (*new in 0.6.11*)
|
||||||
- A user can set sport preferences (*new in 0.5.0*):
|
- A user can set sport preferences (*new in 0.5.0*):
|
||||||
- change sport color (used for sport image and charts)
|
- change sport color (used for sport image and charts)
|
||||||
- can override stopped speed threshold (for next uploaded gpx files)
|
- can override stopped speed threshold (for next uploaded gpx files)
|
||||||
|
|||||||
@@ -710,16 +710,22 @@ Installation
|
|||||||
|
|
||||||
For evaluation purposes, docker files are available, installing **FitTrackee** from **sources**.
|
For evaluation purposes, docker files are available, installing **FitTrackee** from **sources**.
|
||||||
|
|
||||||
- To install **FitTrackee** with database initialisation and run the application and dramatiq workers:
|
- To install **FitTrackee**:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
$ git clone https://github.com/SamR1/FitTrackee.git
|
$ git clone https://github.com/SamR1/FitTrackee.git
|
||||||
$ cd FitTrackee
|
$ cd FitTrackee
|
||||||
$ cp .env.docker .env
|
$ cp .env.docker .env
|
||||||
$ make docker-build docker-run docker-init
|
$ make docker-build
|
||||||
|
|
||||||
Open http://localhost:5000 and register.
|
- To initialise database:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ docker-init
|
||||||
|
|
||||||
|
- Open http://localhost:5000 and register.
|
||||||
|
|
||||||
Open http://localhost:8025 to access `MailHog interface <https://github.com/mailhog/MailHog>`_ (email testing tool)
|
Open http://localhost:8025 to access `MailHog interface <https://github.com/mailhog/MailHog>`_ (email testing tool)
|
||||||
|
|
||||||
@@ -772,4 +778,13 @@ Development
|
|||||||
Open http://localhost:3000
|
Open http://localhost:3000
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Some environment variables need to be updated like `UI_URL`
|
Some environment variables need to be updated like `UI_URL`
|
||||||
|
|
||||||
|
- to run lint or tests:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ make docker-lint-client # run lint on javascript files
|
||||||
|
$ make docker-test-client # run unit tests on Client
|
||||||
|
$ make docker-lint-python # run type check and lint on python files
|
||||||
|
$ make docker-test-python # run unit tests on API
|
||||||
+43
-2
@@ -347,6 +347,7 @@ character “_” allowed</p></li>
|
|||||||
<span class="w"> </span><span class="nt">"bio"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"bio"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"birth_date"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"birth_date"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sun, 14 Jul 2019 14:09:58 GMT"</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sun, 14 Jul 2019 14:09:58 GMT"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"display_ascent"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sam@example.com"</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sam@example.com"</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"imperial_units"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"imperial_units"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"></span>
|
||||||
@@ -377,6 +378,15 @@ character “_” allowed</p></li>
|
|||||||
<span class="w"> </span><span class="nt">"workout_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"workout_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="p">},</span><span class="w"></span>
|
<span class="w"> </span><span class="p">},</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="p">{</span><span class="w"></span>
|
<span class="w"> </span><span class="p">{</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">13</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"record_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"HA"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"sport_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"user"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sam"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"value"</span><span class="p">:</span><span class="w"> </span><span class="mf">43.97</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"workout_date"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sun, 07 Jul 2019 08:00:00 GMT"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"workout_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="p">},</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="p">{</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"record_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LD"</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"record_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LD"</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"sport_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"sport_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
|
||||||
@@ -449,6 +459,7 @@ character “_” allowed</p></li>
|
|||||||
<span class="w"> </span><span class="nt">"bio"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"bio"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"birth_date"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"birth_date"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sun, 14 Jul 2019 14:09:58 GMT"</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sun, 14 Jul 2019 14:09:58 GMT"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"display_ascent"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sam@example.com"</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sam@example.com"</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"imperial_units"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"imperial_units"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"></span>
|
||||||
@@ -479,6 +490,15 @@ character “_” allowed</p></li>
|
|||||||
<span class="w"> </span><span class="nt">"workout_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"workout_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="p">},</span><span class="w"></span>
|
<span class="w"> </span><span class="p">},</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="p">{</span><span class="w"></span>
|
<span class="w"> </span><span class="p">{</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">13</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"record_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"HA"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"sport_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"user"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sam"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"value"</span><span class="p">:</span><span class="w"> </span><span class="mf">43.97</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"workout_date"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sun, 07 Jul 2019 08:00:00 GMT"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"workout_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="p">},</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="p">{</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"record_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LD"</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"record_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LD"</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"sport_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"sport_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
|
||||||
@@ -566,6 +586,7 @@ character “_” allowed</p></li>
|
|||||||
<span class="w"> </span><span class="nt">"bio"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"bio"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"birth_date"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"birth_date"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sun, 14 Jul 2019 14:09:58 GMT"</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sun, 14 Jul 2019 14:09:58 GMT"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"display_ascent"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sam@example.com"</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sam@example.com"</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"imperial_units"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"imperial_units"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"></span>
|
||||||
@@ -596,6 +617,15 @@ character “_” allowed</p></li>
|
|||||||
<span class="w"> </span><span class="nt">"workout_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"workout_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="p">},</span><span class="w"></span>
|
<span class="w"> </span><span class="p">},</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="p">{</span><span class="w"></span>
|
<span class="w"> </span><span class="p">{</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">13</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"record_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"HA"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"sport_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"user"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sam"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"value"</span><span class="p">:</span><span class="w"> </span><span class="mf">43.97</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"workout_date"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sun, 07 Jul 2019 08:00:00 GMT"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"workout_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="p">},</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="p">{</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"record_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LD"</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"record_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LD"</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"sport_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"sport_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
|
||||||
@@ -633,10 +663,11 @@ character “_” allowed</p></li>
|
|||||||
<dl class="field-list simple">
|
<dl class="field-list simple">
|
||||||
<dt class="field-odd">Request JSON Object<span class="colon">:</span></dt>
|
<dt class="field-odd">Request JSON Object<span class="colon">:</span></dt>
|
||||||
<dd class="field-odd"><ul class="simple">
|
<dd class="field-odd"><ul class="simple">
|
||||||
|
<li><p><strong>display_ascent</strong> (<em>boolean</em>) – display highest ascent records and total</p></li>
|
||||||
|
<li><p><strong>imperial_units</strong> (<em>boolean</em>) – display distance in imperial units</p></li>
|
||||||
|
<li><p><strong>language</strong> (<em>string</em>) – language preferences</p></li>
|
||||||
<li><p><strong>timezone</strong> (<em>string</em>) – user time zone</p></li>
|
<li><p><strong>timezone</strong> (<em>string</em>) – user time zone</p></li>
|
||||||
<li><p><strong>weekm</strong> (<em>boolean</em>) – does week start on Monday?</p></li>
|
<li><p><strong>weekm</strong> (<em>boolean</em>) – does week start on Monday?</p></li>
|
||||||
<li><p><strong>language</strong> (<em>string</em>) – language preferences</p></li>
|
|
||||||
<li><p><strong>imperial_units</strong> (<em>boolean</em>) – display distance in imperial units</p></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</dd>
|
</dd>
|
||||||
<dt class="field-even">Request Headers<span class="colon">:</span></dt>
|
<dt class="field-even">Request Headers<span class="colon">:</span></dt>
|
||||||
@@ -924,6 +955,7 @@ character “_” allowed</p></li>
|
|||||||
<span class="w"> </span><span class="nt">"bio"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"bio"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"birth_date"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"birth_date"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sun, 14 Jul 2019 14:09:58 GMT"</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sun, 14 Jul 2019 14:09:58 GMT"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"display_ascent"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sam@example.com"</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"email"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sam@example.com"</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"imperial_units"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"imperial_units"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"></span>
|
||||||
@@ -954,6 +986,15 @@ character “_” allowed</p></li>
|
|||||||
<span class="w"> </span><span class="nt">"workout_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"workout_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="p">},</span><span class="w"></span>
|
<span class="w"> </span><span class="p">},</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="p">{</span><span class="w"></span>
|
<span class="w"> </span><span class="p">{</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">13</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"record_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"HA"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"sport_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"user"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sam"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"value"</span><span class="p">:</span><span class="w"> </span><span class="mf">43.97</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"workout_date"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Sun, 07 Jul 2019 08:00:00 GMT"</span><span class="p">,</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="nt">"workout_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hvYBqYBRa7wwXpaStWR4V2"</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="p">},</span><span class="w"></span>
|
||||||
|
<span class="w"> </span><span class="p">{</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">11</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"record_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LD"</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"record_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LD"</span><span class="p">,</span><span class="w"></span>
|
||||||
<span class="w"> </span><span class="nt">"sport_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
|
<span class="w"> </span><span class="nt">"sport_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
|
||||||
|
|||||||
@@ -212,6 +212,7 @@
|
|||||||
<dt>User records by sports:</dt><dd><ul class="simple">
|
<dt>User records by sports:</dt><dd><ul class="simple">
|
||||||
<li><p>average speed</p></li>
|
<li><p>average speed</p></li>
|
||||||
<li><p>farthest distance</p></li>
|
<li><p>farthest distance</p></li>
|
||||||
|
<li><p>highest ascent (<strong>new in 0.6.11</strong>, can be hidden, see user preferences)</p></li>
|
||||||
<li><p>longest duration</p></li>
|
<li><p>longest duration</p></li>
|
||||||
<li><p>maximum speed</p></li>
|
<li><p>maximum speed</p></li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -248,6 +249,7 @@ A user with an inactive account cannot log in. (<em>new in 0.6.0</em>)</p></li>
|
|||||||
<li><p>A user can reset his password (<em>new in 0.3.0</em>)</p></li>
|
<li><p>A user can reset his password (<em>new in 0.3.0</em>)</p></li>
|
||||||
<li><p>A user can change his email address (<em>new in 0.6.0</em>)</p></li>
|
<li><p>A user can change his email address (<em>new in 0.6.0</em>)</p></li>
|
||||||
<li><p>A user can choose between metric system and imperial system for distance, elevation and speed display (<em>new in 0.5.0</em>)</p></li>
|
<li><p>A user can choose between metric system and imperial system for distance, elevation and speed display (<em>new in 0.5.0</em>)</p></li>
|
||||||
|
<li><p>A user can choose to display or hide ascent records and total on Dashboard (<em>new in 0.6.11</em>)</p></li>
|
||||||
<li><dl class="simple">
|
<li><dl class="simple">
|
||||||
<dt>A user can set sport preferences (<em>new in 0.5.0</em>):</dt><dd><ul>
|
<dt>A user can set sport preferences (<em>new in 0.5.0</em>):</dt><dd><ul>
|
||||||
<li><p>change sport color (used for sport image and charts)</p></li>
|
<li><p>change sport color (used for sport image and charts)</p></li>
|
||||||
|
|||||||
+20
-3
@@ -996,15 +996,23 @@ server {
|
|||||||
</div>
|
</div>
|
||||||
<p>For evaluation purposes, docker files are available, installing <strong>FitTrackee</strong> from <strong>sources</strong>.</p>
|
<p>For evaluation purposes, docker files are available, installing <strong>FitTrackee</strong> from <strong>sources</strong>.</p>
|
||||||
<ul class="simple">
|
<ul class="simple">
|
||||||
<li><p>To install <strong>FitTrackee</strong> with database initialisation and run the application and dramatiq workers:</p></li>
|
<li><p>To install <strong>FitTrackee</strong>:</p></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ git clone https://github.com/SamR1/FitTrackee.git
|
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ git clone https://github.com/SamR1/FitTrackee.git
|
||||||
$ <span class="nb">cd</span> FitTrackee
|
$ <span class="nb">cd</span> FitTrackee
|
||||||
$ cp .env.docker .env
|
$ cp .env.docker .env
|
||||||
$ make docker-build docker-run docker-init
|
$ make docker-build
|
||||||
</pre></div>
|
</pre></div>
|
||||||
</div>
|
</div>
|
||||||
<p>Open <a class="reference external" href="http://localhost:5000">http://localhost:5000</a> and register.</p>
|
<ul class="simple">
|
||||||
|
<li><p>To initialise database:</p></li>
|
||||||
|
</ul>
|
||||||
|
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ docker-init
|
||||||
|
</pre></div>
|
||||||
|
</div>
|
||||||
|
<ul class="simple">
|
||||||
|
<li><p>Open <a class="reference external" href="http://localhost:5000">http://localhost:5000</a> and register.</p></li>
|
||||||
|
</ul>
|
||||||
<p>Open <a class="reference external" href="http://localhost:8025">http://localhost:8025</a> to access <a class="reference external" href="https://github.com/mailhog/MailHog">MailHog interface</a> (email testing tool)</p>
|
<p>Open <a class="reference external" href="http://localhost:8025">http://localhost:8025</a> to access <a class="reference external" href="https://github.com/mailhog/MailHog">MailHog interface</a> (email testing tool)</p>
|
||||||
<ul class="simple">
|
<ul class="simple">
|
||||||
<li><p>To set admin rights to the newly created account, use the following command line:</p></li>
|
<li><p>To set admin rights to the newly created account, use the following command line:</p></li>
|
||||||
@@ -1057,6 +1065,15 @@ $ make docker-build docker-run docker-init
|
|||||||
<p class="admonition-title">Note</p>
|
<p class="admonition-title">Note</p>
|
||||||
<p>Some environment variables need to be updated like <cite>UI_URL</cite></p>
|
<p>Some environment variables need to be updated like <cite>UI_URL</cite></p>
|
||||||
</div>
|
</div>
|
||||||
|
<ul class="simple">
|
||||||
|
<li><p>to run lint or tests:</p></li>
|
||||||
|
</ul>
|
||||||
|
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>$ make docker-lint-client <span class="c1"># run lint on javascript files</span>
|
||||||
|
$ make docker-test-client <span class="c1"># run unit tests on Client</span>
|
||||||
|
$ make docker-lint-python <span class="c1"># run type check and lint on python files</span>
|
||||||
|
$ make docker-test-python <span class="c1"># run unit tests on API</span>
|
||||||
|
</pre></div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -45,6 +45,7 @@ Workouts
|
|||||||
- User records by sports:
|
- User records by sports:
|
||||||
- average speed
|
- average speed
|
||||||
- farthest distance
|
- farthest distance
|
||||||
|
- highest ascent (**new in 0.6.11**, can be hidden, see user preferences)
|
||||||
- longest duration
|
- longest duration
|
||||||
- maximum speed
|
- maximum speed
|
||||||
|
|
||||||
@@ -71,6 +72,7 @@ Account & preferences
|
|||||||
- A user can reset his password (*new in 0.3.0*)
|
- A user can reset his password (*new in 0.3.0*)
|
||||||
- A user can change his email address (*new in 0.6.0*)
|
- A user can change his email address (*new in 0.6.0*)
|
||||||
- A user can choose between metric system and imperial system for distance, elevation and speed display (*new in 0.5.0*)
|
- A user can choose between metric system and imperial system for distance, elevation and speed display (*new in 0.5.0*)
|
||||||
|
- A user can choose to display or hide ascent records and total on Dashboard (*new in 0.6.11*)
|
||||||
- A user can set sport preferences (*new in 0.5.0*):
|
- A user can set sport preferences (*new in 0.5.0*):
|
||||||
- change sport color (used for sport image and charts)
|
- change sport color (used for sport image and charts)
|
||||||
- can override stopped speed threshold (for next uploaded gpx files)
|
- can override stopped speed threshold (for next uploaded gpx files)
|
||||||
|
|||||||
@@ -710,16 +710,22 @@ Installation
|
|||||||
|
|
||||||
For evaluation purposes, docker files are available, installing **FitTrackee** from **sources**.
|
For evaluation purposes, docker files are available, installing **FitTrackee** from **sources**.
|
||||||
|
|
||||||
- To install **FitTrackee** with database initialisation and run the application and dramatiq workers:
|
- To install **FitTrackee**:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
$ git clone https://github.com/SamR1/FitTrackee.git
|
$ git clone https://github.com/SamR1/FitTrackee.git
|
||||||
$ cd FitTrackee
|
$ cd FitTrackee
|
||||||
$ cp .env.docker .env
|
$ cp .env.docker .env
|
||||||
$ make docker-build docker-run docker-init
|
$ make docker-build
|
||||||
|
|
||||||
Open http://localhost:5000 and register.
|
- To initialise database:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ docker-init
|
||||||
|
|
||||||
|
- Open http://localhost:5000 and register.
|
||||||
|
|
||||||
Open http://localhost:8025 to access `MailHog interface <https://github.com/mailhog/MailHog>`_ (email testing tool)
|
Open http://localhost:8025 to access `MailHog interface <https://github.com/mailhog/MailHog>`_ (email testing tool)
|
||||||
|
|
||||||
@@ -772,4 +778,13 @@ Development
|
|||||||
Open http://localhost:3000
|
Open http://localhost:3000
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Some environment variables need to be updated like `UI_URL`
|
Some environment variables need to be updated like `UI_URL`
|
||||||
|
|
||||||
|
- to run lint or tests:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ make docker-lint-client # run lint on javascript files
|
||||||
|
$ make docker-test-client # run unit tests on Client
|
||||||
|
$ make docker-lint-python # run type check and lint on python files
|
||||||
|
$ make docker-test-python # run unit tests on API
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"/><link rel="stylesheet" href="/static/css/leaflet.css"/><title>FitTrackee</title><script defer="defer" src="/static/js/chunk-vendors.7132edc6.js"></script><script defer="defer" src="/static/js/app.bf1d4e1c.js"></script><link href="/static/css/app.32d0ced1.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/static/css/fork-awesome.min.css"/><link rel="stylesheet" href="/static/css/leaflet.css"/><title>FitTrackee</title><script defer="defer" src="/static/js/chunk-vendors.7132edc6.js"></script><script defer="defer" src="/static/js/app.5447516d.js"></script><link href="/static/css/app.f768a44b.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#4DBA87"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="fittrackee_client"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#4DBA87"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but FitTrackee doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[193],{9161:function(e,s,t){t.r(s),t.d(s,{default:function(){return A}});t(6699);var a=t(6252),r=t(2262),l=t(3577),o=t(3324),n=t(9996);const c={class:"chart-menu"},i={class:"chart-arrow"},u={class:"time-frames custom-checkboxes-group"},d={class:"time-frames-checkboxes custom-checkboxes"},p=["id","name","checked","onInput"],m={class:"chart-arrow"};var v=(0,a.aZ)({__name:"StatsMenu",emits:["arrowClick","timeFrameUpdate"],setup(e,{emit:s}){const t=(0,r.iH)("month"),o=["week","month","year"];function n(e){t.value=e,s("timeFrameUpdate",e)}return(e,r)=>((0,a.wg)(),(0,a.iD)("div",c,[(0,a._)("div",i,[(0,a._)("i",{class:"fa fa-chevron-left","aria-hidden":"true",onClick:r[0]||(r[0]=e=>s("arrowClick",!0))})]),(0,a._)("div",u,[(0,a._)("div",d,[((0,a.wg)(),(0,a.iD)(a.HY,null,(0,a.Ko)(o,(s=>(0,a._)("div",{class:"time-frame custom-checkbox",key:s},[(0,a._)("label",null,[(0,a._)("input",{type:"radio",id:s,name:s,checked:t.value===s,onInput:e=>n(s)},null,40,p),(0,a._)("span",null,(0,l.zw)(e.$t(`statistics.TIME_FRAMES.${s}`)),1)])]))),64))])]),(0,a._)("div",m,[(0,a._)("i",{class:"fa fa-chevron-right","aria-hidden":"true",onClick:r[1]||(r[1]=e=>s("arrowClick",!1))})])]))}}),k=t(3744);const _=(0,k.Z)(v,[["__scopeId","data-v-22d55de2"]]);var S=_,w=t(631);const f={class:"sports-menu"},h=["id","name","checked","onInput"],U={class:"sport-label"};var b=(0,a.aZ)({__name:"StatsSportsMenu",props:{userSports:null,selectedSportIds:{default:()=>[]}},emits:["selectedSportIdsUpdate"],setup(e,{emit:s}){const t=e,{t:n}=(0,o.QT)(),c=(0,a.f3)("sportColors"),{selectedSportIds:i}=(0,r.BK)(t),u=(0,a.Fl)((()=>(0,w.xH)(t.userSports,n)));function d(e){s("selectedSportIdsUpdate",e)}return(e,s)=>{const t=(0,a.up)("SportImage");return(0,a.wg)(),(0,a.iD)("div",f,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,r.SU)(u),(e=>((0,a.wg)(),(0,a.iD)("label",{type:"checkbox",key:e.id,style:(0,l.j5)({color:e.color?e.color:(0,r.SU)(c)[e.label]})},[(0,a._)("input",{type:"checkbox",id:e.id,name:e.label,checked:(0,r.SU)(i).includes(e.id),onInput:s=>d(e.id)},null,40,h),(0,a.Wm)(t,{"sport-label":e.label,color:e.color},null,8,["sport-label","color"]),(0,a._)("span",U,(0,l.zw)(e.translatedLabel),1)],4)))),128))])}}});const I=b;var g=I,T=t(9318);const y={key:0,id:"user-statistics"};var C=(0,a.aZ)({__name:"index",props:{sports:null,user:null},setup(e){const s=e,{t:t}=(0,o.QT)(),{sports:l,user:c}=(0,r.BK)(s),i=(0,r.iH)("month"),u=(0,r.iH)(v(i.value)),d=(0,a.Fl)((()=>(0,w.xH)(s.sports,t))),p=(0,r.iH)(_(s.sports));function m(e){i.value=e,u.value=v(i.value)}function v(e){return(0,T.aZ)(new Date,e,s.user.weekm)}function k(e){u.value=(0,T.FN)(u.value,e,s.user.weekm)}function _(e){return e.map((e=>e.id))}function f(e){p.value.includes(e)?p.value=p.value.filter((s=>s!==e)):p.value.push(e)}return(0,a.YP)((()=>s.sports),(e=>{p.value=_(e)})),(e,s)=>(0,r.SU)(d)?((0,a.wg)(),(0,a.iD)("div",y,[(0,a.Wm)(S,{onTimeFrameUpdate:m,onArrowClick:k}),(0,a.Wm)(n.Z,{sports:(0,r.SU)(l),user:(0,r.SU)(c),chartParams:u.value,"displayed-sport-ids":p.value,fullStats:!0},null,8,["sports","user","chartParams","displayed-sport-ids"]),(0,a.Wm)(g,{"selected-sport-ids":p.value,"user-sports":(0,r.SU)(l),onSelectedSportIdsUpdate:f},null,8,["selected-sport-ids","user-sports"])])):(0,a.kq)("",!0)}});const F=(0,k.Z)(C,[["__scopeId","data-v-d693c7da"]]);var Z=F,x=t(5630),D=t(8602),H=t(9917);const E={id:"statistics",class:"view"},R={key:0,class:"container"};var W=(0,a.aZ)({__name:"StatisticsView",setup(e){const s=(0,H.o)(),t=(0,a.Fl)((()=>s.getters[D.YN.GETTERS.AUTH_USER_PROFILE])),o=(0,a.Fl)((()=>s.getters[D.O8.GETTERS.SPORTS].filter((e=>t.value.sports_list.includes(e.id)))));return(e,s)=>{const n=(0,a.up)("Card");return(0,a.wg)(),(0,a.iD)("div",E,[(0,r.SU)(t).username?((0,a.wg)(),(0,a.iD)("div",R,[(0,a.Wm)(n,null,{title:(0,a.w5)((()=>[(0,a.Uk)((0,l.zw)(e.$t("statistics.STATISTICS")),1)])),content:(0,a.w5)((()=>[(0,a.Wm)(Z,{class:(0,l.C_)({"stats-disabled":0===(0,r.SU)(t).nb_workouts}),user:(0,r.SU)(t),sports:(0,r.SU)(o)},null,8,["class","user","sports"])])),_:1}),0===(0,r.SU)(t).nb_workouts?((0,a.wg)(),(0,a.j4)(x.Z,{key:0})):(0,a.kq)("",!0)])):(0,a.kq)("",!0)])}}});const P=(0,k.Z)(W,[["__scopeId","data-v-2e341d4e"]]);var A=P}}]);
|
"use strict";(self["webpackChunkfittrackee_client"]=self["webpackChunkfittrackee_client"]||[]).push([[193],{9161:function(e,s,t){t.r(s),t.d(s,{default:function(){return A}});t(6699);var a=t(6252),r=t(2262),l=t(3577),o=t(3324),n=t(9996);const c={class:"chart-menu"},i={class:"chart-arrow"},u={class:"time-frames custom-checkboxes-group"},d={class:"time-frames-checkboxes custom-checkboxes"},p=["id","name","checked","onInput"],m={class:"chart-arrow"};var v=(0,a.aZ)({__name:"StatsMenu",emits:["arrowClick","timeFrameUpdate"],setup(e,{emit:s}){const t=(0,r.iH)("month"),o=["week","month","year"];function n(e){t.value=e,s("timeFrameUpdate",e)}return(e,r)=>((0,a.wg)(),(0,a.iD)("div",c,[(0,a._)("div",i,[(0,a._)("i",{class:"fa fa-chevron-left","aria-hidden":"true",onClick:r[0]||(r[0]=e=>s("arrowClick",!0))})]),(0,a._)("div",u,[(0,a._)("div",d,[((0,a.wg)(),(0,a.iD)(a.HY,null,(0,a.Ko)(o,(s=>(0,a._)("div",{class:"time-frame custom-checkbox",key:s},[(0,a._)("label",null,[(0,a._)("input",{type:"radio",id:s,name:s,checked:t.value===s,onInput:e=>n(s)},null,40,p),(0,a._)("span",null,(0,l.zw)(e.$t(`statistics.TIME_FRAMES.${s}`)),1)])]))),64))])]),(0,a._)("div",m,[(0,a._)("i",{class:"fa fa-chevron-right","aria-hidden":"true",onClick:r[1]||(r[1]=e=>s("arrowClick",!1))})])]))}}),k=t(3744);const _=(0,k.Z)(v,[["__scopeId","data-v-22d55de2"]]);var S=_,w=t(631);const f={class:"sports-menu"},h=["id","name","checked","onInput"],U={class:"sport-label"};var b=(0,a.aZ)({__name:"StatsSportsMenu",props:{userSports:null,selectedSportIds:{default:()=>[]}},emits:["selectedSportIdsUpdate"],setup(e,{emit:s}){const t=e,{t:n}=(0,o.QT)(),c=(0,a.f3)("sportColors"),{selectedSportIds:i}=(0,r.BK)(t),u=(0,a.Fl)((()=>(0,w.xH)(t.userSports,n)));function d(e){s("selectedSportIdsUpdate",e)}return(e,s)=>{const t=(0,a.up)("SportImage");return(0,a.wg)(),(0,a.iD)("div",f,[((0,a.wg)(!0),(0,a.iD)(a.HY,null,(0,a.Ko)((0,r.SU)(u),(e=>((0,a.wg)(),(0,a.iD)("label",{type:"checkbox",key:e.id,style:(0,l.j5)({color:e.color?e.color:(0,r.SU)(c)[e.label]})},[(0,a._)("input",{type:"checkbox",id:e.id,name:e.label,checked:(0,r.SU)(i).includes(e.id),onInput:s=>d(e.id)},null,40,h),(0,a.Wm)(t,{"sport-label":e.label,color:e.color},null,8,["sport-label","color"]),(0,a._)("span",U,(0,l.zw)(e.translatedLabel),1)],4)))),128))])}}});const I=b;var g=I,T=t(9318);const y={key:0,id:"user-statistics"};var C=(0,a.aZ)({__name:"index",props:{sports:null,user:null},setup(e){const s=e,{t:t}=(0,o.QT)(),{sports:l,user:c}=(0,r.BK)(s),i=(0,r.iH)("month"),u=(0,r.iH)(v(i.value)),d=(0,a.Fl)((()=>(0,w.xH)(s.sports,t))),p=(0,r.iH)(_(s.sports));function m(e){i.value=e,u.value=v(i.value)}function v(e){return(0,T.aZ)(new Date,e,s.user.weekm)}function k(e){u.value=(0,T.FN)(u.value,e,s.user.weekm)}function _(e){return e.map((e=>e.id))}function f(e){p.value.includes(e)?p.value=p.value.filter((s=>s!==e)):p.value.push(e)}return(0,a.YP)((()=>s.sports),(e=>{p.value=_(e)})),(e,s)=>(0,r.SU)(d)?((0,a.wg)(),(0,a.iD)("div",y,[(0,a.Wm)(S,{onTimeFrameUpdate:m,onArrowClick:k}),(0,a.Wm)(n.Z,{sports:(0,r.SU)(l),user:(0,r.SU)(c),chartParams:u.value,"displayed-sport-ids":p.value,fullStats:!0},null,8,["sports","user","chartParams","displayed-sport-ids"]),(0,a.Wm)(g,{"selected-sport-ids":p.value,"user-sports":(0,r.SU)(l),onSelectedSportIdsUpdate:f},null,8,["selected-sport-ids","user-sports"])])):(0,a.kq)("",!0)}});const F=(0,k.Z)(C,[["__scopeId","data-v-d693c7da"]]);var Z=F,x=t(5630),D=t(8602),H=t(9917);const E={id:"statistics",class:"view"},R={key:0,class:"container"};var W=(0,a.aZ)({__name:"StatisticsView",setup(e){const s=(0,H.o)(),t=(0,a.Fl)((()=>s.getters[D.YN.GETTERS.AUTH_USER_PROFILE])),o=(0,a.Fl)((()=>s.getters[D.O8.GETTERS.SPORTS].filter((e=>t.value.sports_list.includes(e.id)))));return(e,s)=>{const n=(0,a.up)("Card");return(0,a.wg)(),(0,a.iD)("div",E,[(0,r.SU)(t).username?((0,a.wg)(),(0,a.iD)("div",R,[(0,a.Wm)(n,null,{title:(0,a.w5)((()=>[(0,a.Uk)((0,l.zw)(e.$t("statistics.STATISTICS")),1)])),content:(0,a.w5)((()=>[(0,a.Wm)(Z,{class:(0,l.C_)({"stats-disabled":0===(0,r.SU)(t).nb_workouts}),user:(0,r.SU)(t),sports:(0,r.SU)(o)},null,8,["class","user","sports"])])),_:1}),0===(0,r.SU)(t).nb_workouts?((0,a.wg)(),(0,a.j4)(x.Z,{key:0})):(0,a.kq)("",!0)])):(0,a.kq)("",!0)])}}});const P=(0,k.Z)(W,[["__scopeId","data-v-2e341d4e"]]);var A=P}}]);
|
||||||
//# sourceMappingURL=statistics.1ad194e3.js.map
|
//# sourceMappingURL=statistics.ef50f3c2.js.map
|
||||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,45 @@
|
|||||||
|
"""add ascent record
|
||||||
|
|
||||||
|
Revision ID: cd0e6cf83207
|
||||||
|
Revises: 5e3a3a31c432
|
||||||
|
Create Date: 2022-03-22 20:21:13.661883
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'cd0e6cf83207'
|
||||||
|
down_revision = '5e3a3a31c432'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
ALTER TYPE record_types ADD VALUE 'HA';
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
op.add_column(
|
||||||
|
'users', sa.Column('display_ascent', sa.Boolean(), nullable=True)
|
||||||
|
)
|
||||||
|
op.execute("UPDATE users SET display_ascent = true")
|
||||||
|
op.alter_column('users', 'display_ascent', nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_column('users', 'display_ascent')
|
||||||
|
|
||||||
|
op.execute("DELETE FROM records WHERE record_type = 'HA';")
|
||||||
|
op.execute("ALTER TYPE record_types RENAME TO record_types_old")
|
||||||
|
op.execute("CREATE TYPE record_types AS ENUM('AS', 'FD', 'LD', 'MS')")
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
ALTER TABLE records ALTER COLUMN record_type TYPE record_types
|
||||||
|
USING record_type::text::record_types
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
op.execute("DROP TYPE record_types_old")
|
||||||
@@ -1272,6 +1272,7 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin):
|
|||||||
weekm=True,
|
weekm=True,
|
||||||
language=input_language,
|
language=input_language,
|
||||||
imperial_units=True,
|
imperial_units=True,
|
||||||
|
display_ascent=False,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
headers=dict(Authorization=f'Bearer {auth_token}'),
|
headers=dict(Authorization=f'Bearer {auth_token}'),
|
||||||
@@ -1281,8 +1282,11 @@ class TestUserPreferencesUpdate(ApiTestCaseMixin):
|
|||||||
data = json.loads(response.data.decode())
|
data = json.loads(response.data.decode())
|
||||||
assert data['status'] == 'success'
|
assert data['status'] == 'success'
|
||||||
assert data['message'] == 'user preferences updated'
|
assert data['message'] == 'user preferences updated'
|
||||||
|
assert data['data']['display_ascent'] is False
|
||||||
|
assert data['data']['imperial_units'] is True
|
||||||
assert data['data']['language'] == expected_language
|
assert data['data']['language'] == expected_language
|
||||||
assert data['data'] == jsonify_dict(user_1.serialize(user_1))
|
assert data['data']['timezone'] == 'America/New_York'
|
||||||
|
assert data['data']['weekm'] is True
|
||||||
|
|
||||||
|
|
||||||
class TestUserSportPreferencesUpdate(ApiTestCaseMixin):
|
class TestUserSportPreferencesUpdate(ApiTestCaseMixin):
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class UserModelAssertMixin:
|
|||||||
assert 'nb_workouts' in serialized_user
|
assert 'nb_workouts' in serialized_user
|
||||||
assert 'records' in serialized_user
|
assert 'records' in serialized_user
|
||||||
assert 'sports_list' in serialized_user
|
assert 'sports_list' in serialized_user
|
||||||
|
assert 'total_ascent' in serialized_user
|
||||||
assert 'total_distance' in serialized_user
|
assert 'total_distance' in serialized_user
|
||||||
assert 'total_duration' in serialized_user
|
assert 'total_duration' in serialized_user
|
||||||
|
|
||||||
@@ -66,6 +67,7 @@ class TestUserSerializeAsAuthUser(UserModelAssertMixin):
|
|||||||
assert serialized_user['language'] == user_1.language
|
assert serialized_user['language'] == user_1.language
|
||||||
assert serialized_user['timezone'] == user_1.timezone
|
assert serialized_user['timezone'] == user_1.timezone
|
||||||
assert serialized_user['weekm'] == user_1.weekm
|
assert serialized_user['weekm'] == user_1.weekm
|
||||||
|
assert serialized_user['display_ascent'] == user_1.display_ascent
|
||||||
|
|
||||||
def test_it_returns_workouts_infos(self, app: Flask, user_1: User) -> None:
|
def test_it_returns_workouts_infos(self, app: Flask, user_1: User) -> None:
|
||||||
serialized_user = user_1.serialize(user_1)
|
serialized_user = user_1.serialize(user_1)
|
||||||
@@ -168,6 +170,46 @@ class TestUserRecords(UserModelAssertMixin):
|
|||||||
)
|
)
|
||||||
assert serialized_user['records'][0]['workout_date']
|
assert serialized_user['records'][0]['workout_date']
|
||||||
|
|
||||||
|
def test_it_returns_totals_when_user_has_workout_without_ascent(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
user_1: User,
|
||||||
|
sport_1_cycling: Sport,
|
||||||
|
workout_cycling_user_1: Workout,
|
||||||
|
) -> None:
|
||||||
|
serialized_user = user_1.serialize(user_1)
|
||||||
|
assert serialized_user['total_ascent'] == 0
|
||||||
|
assert serialized_user['total_distance'] == 10
|
||||||
|
assert serialized_user['total_duration'] == '1:00:00'
|
||||||
|
|
||||||
|
def test_it_returns_totals_when_user_has_workout_with_ascent(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
user_1: User,
|
||||||
|
sport_1_cycling: Sport,
|
||||||
|
workout_cycling_user_1: Workout,
|
||||||
|
) -> None:
|
||||||
|
workout_cycling_user_1.ascent = 100
|
||||||
|
serialized_user = user_1.serialize(user_1)
|
||||||
|
assert serialized_user['total_ascent'] == 100
|
||||||
|
assert serialized_user['total_distance'] == 10
|
||||||
|
assert serialized_user['total_duration'] == '1:00:00'
|
||||||
|
|
||||||
|
def test_it_returns_totals_when_user_has_mutiple_workouts(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
user_1: User,
|
||||||
|
sport_1_cycling: Sport,
|
||||||
|
sport_2_running: Sport,
|
||||||
|
workout_cycling_user_1: Workout,
|
||||||
|
workout_running_user_1: Workout,
|
||||||
|
) -> None:
|
||||||
|
workout_cycling_user_1.ascent = 100
|
||||||
|
serialized_user = user_1.serialize(user_1)
|
||||||
|
assert serialized_user['total_ascent'] == 100
|
||||||
|
assert serialized_user['total_distance'] == 22
|
||||||
|
assert serialized_user['total_duration'] == '2:40:00'
|
||||||
|
|
||||||
|
|
||||||
class TestUserWorkouts(UserModelAssertMixin):
|
class TestUserWorkouts(UserModelAssertMixin):
|
||||||
def test_it_returns_infos_when_no_workouts(
|
def test_it_returns_infos_when_no_workouts(
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ def assert_workout_data_with_gpx(data: Dict) -> None:
|
|||||||
assert segment['pauses'] is None
|
assert segment['pauses'] is None
|
||||||
|
|
||||||
records = data['data']['workouts'][0]['records']
|
records = data['data']['workouts'][0]['records']
|
||||||
assert len(records) == 4
|
assert len(records) == 5
|
||||||
assert records[0]['sport_id'] == 1
|
assert records[0]['sport_id'] == 1
|
||||||
assert records[0]['workout_id'] == data['data']['workouts'][0]['id']
|
assert records[0]['workout_id'] == data['data']['workouts'][0]['id']
|
||||||
assert records[0]['record_type'] == 'MS'
|
assert records[0]['record_type'] == 'MS'
|
||||||
@@ -69,14 +69,19 @@ def assert_workout_data_with_gpx(data: Dict) -> None:
|
|||||||
assert records[1]['value'] == '0:04:10'
|
assert records[1]['value'] == '0:04:10'
|
||||||
assert records[2]['sport_id'] == 1
|
assert records[2]['sport_id'] == 1
|
||||||
assert records[2]['workout_id'] == data['data']['workouts'][0]['id']
|
assert records[2]['workout_id'] == data['data']['workouts'][0]['id']
|
||||||
assert records[2]['record_type'] == 'FD'
|
assert records[2]['record_type'] == 'HA'
|
||||||
|
assert records[2]['value'] == 0.4
|
||||||
assert records[2]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
assert records[2]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||||
assert records[2]['value'] == 0.32
|
|
||||||
assert records[3]['sport_id'] == 1
|
assert records[3]['sport_id'] == 1
|
||||||
assert records[3]['workout_id'] == data['data']['workouts'][0]['id']
|
assert records[3]['workout_id'] == data['data']['workouts'][0]['id']
|
||||||
assert records[3]['record_type'] == 'AS'
|
assert records[3]['record_type'] == 'FD'
|
||||||
assert records[3]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
assert records[3]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||||
assert records[3]['value'] == 4.61
|
assert records[3]['value'] == 0.32
|
||||||
|
assert records[4]['sport_id'] == 1
|
||||||
|
assert records[4]['workout_id'] == data['data']['workouts'][0]['id']
|
||||||
|
assert records[4]['record_type'] == 'AS'
|
||||||
|
assert records[4]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||||
|
assert records[4]['value'] == 4.61
|
||||||
|
|
||||||
|
|
||||||
def assert_workout_data_with_gpx_segments(data: Dict) -> None:
|
def assert_workout_data_with_gpx_segments(data: Dict) -> None:
|
||||||
@@ -133,7 +138,7 @@ def assert_workout_data_with_gpx_segments(data: Dict) -> None:
|
|||||||
assert segment['pauses'] is None
|
assert segment['pauses'] is None
|
||||||
|
|
||||||
records = data['data']['workouts'][0]['records']
|
records = data['data']['workouts'][0]['records']
|
||||||
assert len(records) == 4
|
assert len(records) == 5
|
||||||
assert records[0]['sport_id'] == 1
|
assert records[0]['sport_id'] == 1
|
||||||
assert records[0]['workout_id'] == data['data']['workouts'][0]['id']
|
assert records[0]['workout_id'] == data['data']['workouts'][0]['id']
|
||||||
assert records[0]['record_type'] == 'MS'
|
assert records[0]['record_type'] == 'MS'
|
||||||
@@ -146,14 +151,18 @@ def assert_workout_data_with_gpx_segments(data: Dict) -> None:
|
|||||||
assert records[1]['value'] == '0:03:55'
|
assert records[1]['value'] == '0:03:55'
|
||||||
assert records[2]['sport_id'] == 1
|
assert records[2]['sport_id'] == 1
|
||||||
assert records[2]['workout_id'] == data['data']['workouts'][0]['id']
|
assert records[2]['workout_id'] == data['data']['workouts'][0]['id']
|
||||||
assert records[2]['record_type'] == 'FD'
|
assert records[2]['record_type'] == 'HA'
|
||||||
assert records[2]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
assert records[2]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||||
assert records[2]['value'] == 0.3
|
|
||||||
assert records[3]['sport_id'] == 1
|
assert records[3]['sport_id'] == 1
|
||||||
assert records[3]['workout_id'] == data['data']['workouts'][0]['id']
|
assert records[3]['workout_id'] == data['data']['workouts'][0]['id']
|
||||||
assert records[3]['record_type'] == 'AS'
|
assert records[3]['record_type'] == 'FD'
|
||||||
assert records[3]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
assert records[3]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||||
assert records[3]['value'] == 4.59
|
assert records[3]['value'] == 0.3
|
||||||
|
assert records[4]['sport_id'] == 1
|
||||||
|
assert records[4]['workout_id'] == data['data']['workouts'][0]['id']
|
||||||
|
assert records[4]['record_type'] == 'AS'
|
||||||
|
assert records[4]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||||
|
assert records[4]['value'] == 4.59
|
||||||
|
|
||||||
|
|
||||||
def assert_workout_data_wo_gpx(data: Dict) -> None:
|
def assert_workout_data_wo_gpx(data: Dict) -> None:
|
||||||
@@ -252,6 +261,39 @@ class TestPostWorkoutWithGpx(ApiTestCaseMixin, CallArgsMixin):
|
|||||||
assert 'just a workout' == data['data']['workouts'][0]['title']
|
assert 'just a workout' == data['data']['workouts'][0]['title']
|
||||||
assert_workout_data_with_gpx(data)
|
assert_workout_data_with_gpx(data)
|
||||||
|
|
||||||
|
def test_it_returns_ha_record_when_a_workout_without_gpx_exists(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
user_1: User,
|
||||||
|
sport_1_cycling: Sport,
|
||||||
|
gpx_file: str,
|
||||||
|
workout_cycling_user_1: Workout,
|
||||||
|
) -> None:
|
||||||
|
client, auth_token = self.get_test_client_and_auth_token(
|
||||||
|
app, user_1.email
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
'/api/workouts',
|
||||||
|
data=dict(
|
||||||
|
file=(BytesIO(str.encode(gpx_file)), 'example.gpx'),
|
||||||
|
data='{"sport_id": 1}',
|
||||||
|
),
|
||||||
|
headers=dict(
|
||||||
|
content_type='multipart/form-data',
|
||||||
|
Authorization=f'Bearer {auth_token}',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
data = json.loads(response.data.decode())
|
||||||
|
records = data['data']['workouts'][0]['records']
|
||||||
|
assert len(records) == 1
|
||||||
|
assert records[0]['sport_id'] == 1
|
||||||
|
assert records[0]['workout_id'] == data['data']['workouts'][0]['id']
|
||||||
|
assert records[0]['record_type'] == 'HA'
|
||||||
|
assert records[0]['value'] == 0.4
|
||||||
|
assert records[0]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||||
|
|
||||||
def test_it_creates_workout_with_expecting_gpx_path(
|
def test_it_creates_workout_with_expecting_gpx_path(
|
||||||
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
|
self, app: Flask, user_1: User, sport_1_cycling: Sport, gpx_file: str
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ def assert_workout_data_with_gpx(data: Dict, sport_id: int) -> None:
|
|||||||
assert data['data']['workouts'][0]['with_gpx'] is True
|
assert data['data']['workouts'][0]['with_gpx'] is True
|
||||||
|
|
||||||
records = data['data']['workouts'][0]['records']
|
records = data['data']['workouts'][0]['records']
|
||||||
assert len(records) == 4
|
assert len(records) == 5
|
||||||
assert records[0]['sport_id'] == sport_id
|
assert records[0]['sport_id'] == sport_id
|
||||||
assert records[0]['workout_id'] == data['data']['workouts'][0]['id']
|
assert records[0]['workout_id'] == data['data']['workouts'][0]['id']
|
||||||
assert records[0]['record_type'] == 'MS'
|
assert records[0]['record_type'] == 'MS'
|
||||||
@@ -45,14 +45,18 @@ def assert_workout_data_with_gpx(data: Dict, sport_id: int) -> None:
|
|||||||
assert records[1]['value'] == '0:04:10'
|
assert records[1]['value'] == '0:04:10'
|
||||||
assert records[2]['sport_id'] == sport_id
|
assert records[2]['sport_id'] == sport_id
|
||||||
assert records[2]['workout_id'] == data['data']['workouts'][0]['id']
|
assert records[2]['workout_id'] == data['data']['workouts'][0]['id']
|
||||||
assert records[2]['record_type'] == 'FD'
|
assert records[2]['record_type'] == 'HA'
|
||||||
assert records[2]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
assert records[2]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||||
assert records[2]['value'] == 0.32
|
|
||||||
assert records[3]['sport_id'] == sport_id
|
assert records[3]['sport_id'] == sport_id
|
||||||
assert records[3]['workout_id'] == data['data']['workouts'][0]['id']
|
assert records[3]['workout_id'] == data['data']['workouts'][0]['id']
|
||||||
assert records[3]['record_type'] == 'AS'
|
assert records[3]['record_type'] == 'FD'
|
||||||
assert records[3]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
assert records[3]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||||
assert records[3]['value'] == 4.61
|
assert records[3]['value'] == 0.32
|
||||||
|
assert records[4]['sport_id'] == sport_id
|
||||||
|
assert records[4]['workout_id'] == data['data']['workouts'][0]['id']
|
||||||
|
assert records[4]['record_type'] == 'AS'
|
||||||
|
assert records[4]['workout_date'] == 'Tue, 13 Mar 2018 12:44:45 GMT'
|
||||||
|
assert records[4]['value'] == 4.61
|
||||||
|
|
||||||
|
|
||||||
class TestEditWorkoutWithGpx(ApiTestCaseMixin):
|
class TestEditWorkoutWithGpx(ApiTestCaseMixin):
|
||||||
|
|||||||
@@ -290,6 +290,7 @@ def get_authenticated_user_profile(
|
|||||||
"bio": null,
|
"bio": null,
|
||||||
"birth_date": null,
|
"birth_date": null,
|
||||||
"created_at": "Sun, 14 Jul 2019 14:09:58 GMT",
|
"created_at": "Sun, 14 Jul 2019 14:09:58 GMT",
|
||||||
|
"display_ascent": true,
|
||||||
"email": "sam@example.com",
|
"email": "sam@example.com",
|
||||||
"first_name": null,
|
"first_name": null,
|
||||||
"imperial_units": false,
|
"imperial_units": false,
|
||||||
@@ -319,6 +320,15 @@ def get_authenticated_user_profile(
|
|||||||
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||||
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"record_type": "HA",
|
||||||
|
"sport_id": 1,
|
||||||
|
"user": "Sam",
|
||||||
|
"value": 43.97,
|
||||||
|
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||||
|
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": 11,
|
"id": 11,
|
||||||
"record_type": "LD",
|
"record_type": "LD",
|
||||||
@@ -390,6 +400,7 @@ def edit_user(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
"bio": null,
|
"bio": null,
|
||||||
"birth_date": null,
|
"birth_date": null,
|
||||||
"created_at": "Sun, 14 Jul 2019 14:09:58 GMT",
|
"created_at": "Sun, 14 Jul 2019 14:09:58 GMT",
|
||||||
|
"display_ascent": true,
|
||||||
"email": "sam@example.com",
|
"email": "sam@example.com",
|
||||||
"first_name": null,
|
"first_name": null,
|
||||||
"imperial_units": false,
|
"imperial_units": false,
|
||||||
@@ -419,6 +430,15 @@ def edit_user(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||||
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"record_type": "HA",
|
||||||
|
"sport_id": 1,
|
||||||
|
"user": "Sam",
|
||||||
|
"value": 43.97,
|
||||||
|
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||||
|
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": 11,
|
"id": 11,
|
||||||
"record_type": "LD",
|
"record_type": "LD",
|
||||||
@@ -546,6 +566,7 @@ def update_user_account(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
"bio": null,
|
"bio": null,
|
||||||
"birth_date": null,
|
"birth_date": null,
|
||||||
"created_at": "Sun, 14 Jul 2019 14:09:58 GMT",
|
"created_at": "Sun, 14 Jul 2019 14:09:58 GMT",
|
||||||
|
"display_ascent": true,
|
||||||
"email": "sam@example.com",
|
"email": "sam@example.com",
|
||||||
"first_name": null,
|
"first_name": null,
|
||||||
"imperial_units": false,
|
"imperial_units": false,
|
||||||
@@ -575,6 +596,15 @@ def update_user_account(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||||
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"record_type": "HA",
|
||||||
|
"sport_id": 1,
|
||||||
|
"user": "Sam",
|
||||||
|
"value": 43.97,
|
||||||
|
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||||
|
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": 11,
|
"id": 11,
|
||||||
"record_type": "LD",
|
"record_type": "LD",
|
||||||
@@ -746,6 +776,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
"bio": null,
|
"bio": null,
|
||||||
"birth_date": null,
|
"birth_date": null,
|
||||||
"created_at": "Sun, 14 Jul 2019 14:09:58 GMT",
|
"created_at": "Sun, 14 Jul 2019 14:09:58 GMT",
|
||||||
|
"display_ascent": true,
|
||||||
"email": "sam@example.com",
|
"email": "sam@example.com",
|
||||||
"first_name": null,
|
"first_name": null,
|
||||||
"imperial_units": false,
|
"imperial_units": false,
|
||||||
@@ -775,6 +806,15 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||||
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"record_type": "HA",
|
||||||
|
"sport_id": 1,
|
||||||
|
"user": "Sam",
|
||||||
|
"value": 43.97,
|
||||||
|
"workout_date": "Sun, 07 Jul 2019 08:00:00 GMT",
|
||||||
|
"workout_id": "hvYBqYBRa7wwXpaStWR4V2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": 11,
|
"id": 11,
|
||||||
"record_type": "LD",
|
"record_type": "LD",
|
||||||
@@ -809,10 +849,11 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
"status": "success"
|
"status": "success"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:<json boolean display_ascent: display highest ascent records and total
|
||||||
|
:<json boolean imperial_units: display distance in imperial units
|
||||||
|
:<json string language: language preferences
|
||||||
:<json string timezone: user time zone
|
:<json string timezone: user time zone
|
||||||
:<json boolean weekm: does week start on Monday?
|
:<json boolean weekm: does week start on Monday?
|
||||||
:<json string language: language preferences
|
|
||||||
:<json boolean imperial_units: display distance in imperial units
|
|
||||||
|
|
||||||
:reqheader Authorization: OAuth 2.0 Bearer Token
|
:reqheader Authorization: OAuth 2.0 Bearer Token
|
||||||
|
|
||||||
@@ -830,6 +871,7 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
# get post data
|
# get post data
|
||||||
post_data = request.get_json()
|
post_data = request.get_json()
|
||||||
user_mandatory_data = {
|
user_mandatory_data = {
|
||||||
|
'display_ascent',
|
||||||
'imperial_units',
|
'imperial_units',
|
||||||
'language',
|
'language',
|
||||||
'timezone',
|
'timezone',
|
||||||
@@ -838,12 +880,14 @@ def edit_user_preferences(auth_user: User) -> Union[Dict, HttpResponse]:
|
|||||||
if not post_data or not post_data.keys() >= user_mandatory_data:
|
if not post_data or not post_data.keys() >= user_mandatory_data:
|
||||||
return InvalidPayloadErrorResponse()
|
return InvalidPayloadErrorResponse()
|
||||||
|
|
||||||
|
display_ascent = post_data.get('display_ascent')
|
||||||
imperial_units = post_data.get('imperial_units')
|
imperial_units = post_data.get('imperial_units')
|
||||||
language = get_language(post_data.get('language'))
|
language = get_language(post_data.get('language'))
|
||||||
timezone = post_data.get('timezone')
|
timezone = post_data.get('timezone')
|
||||||
weekm = post_data.get('weekm')
|
weekm = post_data.get('weekm')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
auth_user.display_ascent = display_ascent
|
||||||
auth_user.imperial_units = imperial_units
|
auth_user.imperial_units = imperial_units
|
||||||
auth_user.language = language
|
auth_user.language = language
|
||||||
auth_user.timezone = timezone
|
auth_user.timezone = timezone
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class User(BaseModel):
|
|||||||
is_active = db.Column(db.Boolean, default=False, nullable=False)
|
is_active = db.Column(db.Boolean, default=False, nullable=False)
|
||||||
email_to_confirm = db.Column(db.String(255), nullable=True)
|
email_to_confirm = db.Column(db.String(255), nullable=True)
|
||||||
confirmation_token = db.Column(db.String(255), nullable=True)
|
confirmation_token = db.Column(db.String(255), nullable=True)
|
||||||
|
display_ascent = db.Column(db.Boolean, default=True, nullable=False)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'<User {self.username!r}>'
|
return f'<User {self.username!r}>'
|
||||||
@@ -127,7 +128,7 @@ class User(BaseModel):
|
|||||||
raise UserNotFoundException()
|
raise UserNotFoundException()
|
||||||
|
|
||||||
sports = []
|
sports = []
|
||||||
total = (0, '0:00:00')
|
total = (0, '0:00:00', 0)
|
||||||
if self.workouts_count > 0: # type: ignore
|
if self.workouts_count > 0: # type: ignore
|
||||||
sports = (
|
sports = (
|
||||||
db.session.query(Workout.sport_id)
|
db.session.query(Workout.sport_id)
|
||||||
@@ -138,7 +139,9 @@ class User(BaseModel):
|
|||||||
)
|
)
|
||||||
total = (
|
total = (
|
||||||
db.session.query(
|
db.session.query(
|
||||||
func.sum(Workout.distance), func.sum(Workout.duration)
|
func.sum(Workout.distance),
|
||||||
|
func.sum(Workout.duration),
|
||||||
|
func.sum(Workout.ascent),
|
||||||
)
|
)
|
||||||
.filter(Workout.user_id == self.id)
|
.filter(Workout.user_id == self.id)
|
||||||
.first()
|
.first()
|
||||||
@@ -162,6 +165,7 @@ class User(BaseModel):
|
|||||||
'sports_list': [
|
'sports_list': [
|
||||||
sport for sportslist in sports for sport in sportslist
|
sport for sportslist in sports for sport in sportslist
|
||||||
],
|
],
|
||||||
|
'total_ascent': float(total[2]) if total[2] else 0.0,
|
||||||
'total_distance': float(total[0]),
|
'total_distance': float(total[0]),
|
||||||
'total_duration': str(total[1]),
|
'total_duration': str(total[1]),
|
||||||
'username': self.username,
|
'username': self.username,
|
||||||
@@ -170,6 +174,7 @@ class User(BaseModel):
|
|||||||
serialized_user = {
|
serialized_user = {
|
||||||
**serialized_user,
|
**serialized_user,
|
||||||
**{
|
**{
|
||||||
|
'display_ascent': self.display_ascent,
|
||||||
'imperial_units': self.imperial_units,
|
'imperial_units': self.imperial_units,
|
||||||
'language': self.language,
|
'language': self.language,
|
||||||
'timezone': self.timezone,
|
'timezone': self.timezone,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ BaseModel: DeclarativeMeta = db.Model
|
|||||||
record_types = [
|
record_types = [
|
||||||
'AS', # 'Best Average Speed'
|
'AS', # 'Best Average Speed'
|
||||||
'FD', # 'Farthest Distance'
|
'FD', # 'Farthest Distance'
|
||||||
|
'HA', # 'Highest Ascent'
|
||||||
'LD', # 'Longest Duration'
|
'LD', # 'Longest Duration'
|
||||||
'MS', # 'Max speed'
|
'MS', # 'Max speed'
|
||||||
]
|
]
|
||||||
@@ -319,9 +320,14 @@ class Workout(BaseModel):
|
|||||||
def get_user_workout_records(
|
def get_user_workout_records(
|
||||||
cls, user_id: int, sport_id: int, as_integer: Optional[bool] = False
|
cls, user_id: int, sport_id: int, as_integer: Optional[bool] = False
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
|
"""
|
||||||
|
Note:
|
||||||
|
Values for ascent are null for workouts without gpx
|
||||||
|
"""
|
||||||
record_types_columns = {
|
record_types_columns = {
|
||||||
'AS': 'ave_speed', # 'Average speed'
|
'AS': 'ave_speed', # 'Average speed'
|
||||||
'FD': 'distance', # 'Farthest Distance'
|
'FD': 'distance', # 'Farthest Distance'
|
||||||
|
'HA': 'ascent', # 'Highest Ascent'
|
||||||
'LD': 'moving', # 'Longest Duration'
|
'LD': 'moving', # 'Longest Duration'
|
||||||
'MS': 'max_speed', # 'Max speed'
|
'MS': 'max_speed', # 'Max speed'
|
||||||
}
|
}
|
||||||
@@ -329,7 +335,11 @@ class Workout(BaseModel):
|
|||||||
for record_type, column in record_types_columns.items():
|
for record_type, column in record_types_columns.items():
|
||||||
column_sorted = getattr(getattr(Workout, column), 'desc')()
|
column_sorted = getattr(getattr(Workout, column), 'desc')()
|
||||||
record_workout = (
|
record_workout = (
|
||||||
Workout.query.filter_by(user_id=user_id, sport_id=sport_id)
|
Workout.query.filter(
|
||||||
|
Workout.user_id == user_id,
|
||||||
|
Workout.sport_id == sport_id,
|
||||||
|
getattr(Workout, column) != None, # noqa
|
||||||
|
)
|
||||||
.order_by(column_sorted, Workout.workout_date)
|
.order_by(column_sorted, Workout.workout_date)
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
@@ -481,7 +491,7 @@ class Record(BaseModel):
|
|||||||
return datetime.timedelta(seconds=self._value)
|
return datetime.timedelta(seconds=self._value)
|
||||||
elif self.record_type in ['AS', 'MS']:
|
elif self.record_type in ['AS', 'MS']:
|
||||||
return float(self._value / 100)
|
return float(self._value / 100)
|
||||||
else: # 'FD'
|
else: # 'FD' or 'HA'
|
||||||
return float(self._value / 1000)
|
return float(self._value / 1000)
|
||||||
|
|
||||||
@value.setter # type: ignore
|
@value.setter # type: ignore
|
||||||
@@ -491,7 +501,7 @@ class Record(BaseModel):
|
|||||||
def serialize(self) -> Dict:
|
def serialize(self) -> Dict:
|
||||||
if self.value is None:
|
if self.value is None:
|
||||||
value = None
|
value = None
|
||||||
elif self.record_type in ['AS', 'FD', 'MS']:
|
elif self.record_type in ['AS', 'FD', 'HA', 'MS']:
|
||||||
value = float(self.value) # type: ignore
|
value = float(self.value) # type: ignore
|
||||||
else: # 'LD'
|
else: # 'LD'
|
||||||
value = str(self.value) # type: ignore
|
value = str(self.value) # type: ignore
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
props.user.records,
|
props.user.records,
|
||||||
translateSports(props.sports, t),
|
translateSports(props.sports, t),
|
||||||
props.user.timezone,
|
props.user.timezone,
|
||||||
props.user.imperial_units
|
props.user.imperial_units,
|
||||||
|
props.user.display_ascent
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,7 +8,13 @@
|
|||||||
<StatCard
|
<StatCard
|
||||||
icon="road"
|
icon="road"
|
||||||
:value="totalDistance"
|
:value="totalDistance"
|
||||||
:text="unitTo === 'mi' ? 'miles' : unitTo"
|
:text="distanceUnitTo === 'mi' ? 'miles' : distanceUnitTo"
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
v-if="user.display_ascent"
|
||||||
|
icon="location-arrow"
|
||||||
|
:value="totalAscent"
|
||||||
|
:text="ascentUnitTo === 'ft' ? 'feet' : ascentUnitTo"
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
icon="clock-o"
|
icon="clock-o"
|
||||||
@@ -16,6 +22,7 @@
|
|||||||
:text="totalDuration.duration"
|
:text="totalDuration.duration"
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
|
v-if="!user.display_ascent"
|
||||||
icon="tags"
|
icon="tags"
|
||||||
:value="user.nb_sports"
|
:value="user.nb_sports"
|
||||||
:text="$t('workouts.SPORT', user.nb_sports)"
|
:text="$t('workouts.SPORT', user.nb_sports)"
|
||||||
@@ -43,15 +50,23 @@
|
|||||||
() => props.user.total_duration
|
() => props.user.total_duration
|
||||||
)
|
)
|
||||||
const totalDuration = computed(() => get_duration(userTotalDuration))
|
const totalDuration = computed(() => get_duration(userTotalDuration))
|
||||||
const defaultUnitFrom: TUnit = 'km'
|
const distanceUnitFrom: TUnit = 'km'
|
||||||
const unitTo: TUnit = user.value.imperial_units
|
const distanceUnitTo: TUnit = user.value.imperial_units
|
||||||
? units[defaultUnitFrom].defaultTarget
|
? units[distanceUnitFrom].defaultTarget
|
||||||
: defaultUnitFrom
|
: distanceUnitFrom
|
||||||
const totalDistance: ComputedRef<number> = computed(() =>
|
const totalDistance: ComputedRef<number> = computed(() =>
|
||||||
user.value.imperial_units
|
user.value.imperial_units
|
||||||
? convertDistance(user.value.total_distance, defaultUnitFrom, unitTo, 2)
|
? convertDistance(user.value.total_distance, distanceUnitFrom, distanceUnitTo, 2)
|
||||||
: parseFloat(user.value.total_distance.toFixed(2))
|
: parseFloat(user.value.total_distance.toFixed(2)))
|
||||||
)
|
const ascentUnitFrom: TUnit = 'm'
|
||||||
|
const ascentUnitTo: TUnit = user.value.imperial_units
|
||||||
|
? units[ascentUnitFrom].defaultTarget
|
||||||
|
: ascentUnitFrom
|
||||||
|
const totalAscent: ComputedRef<number> = computed(() =>
|
||||||
|
user.value.imperial_units
|
||||||
|
? convertDistance(user.value.total_ascent, ascentUnitFrom, ascentUnitTo, 2)
|
||||||
|
: parseFloat(user.value.total_ascent.toFixed(2)))
|
||||||
|
|
||||||
|
|
||||||
function get_duration(total_duration: ComputedRef<string>) {
|
function get_duration(total_duration: ComputedRef<string>) {
|
||||||
const duration = total_duration.value.match(/day/g)
|
const duration = total_duration.value.match(/day/g)
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</dd>
|
</dd>
|
||||||
|
<dt>{{ $t('user.PROFILE.ASCENT_DATA') }}:</dt>
|
||||||
|
<dd>{{ $t(`common.${display_ascent}`) }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<div class="profile-buttons">
|
<div class="profile-buttons">
|
||||||
<button @click="$router.push('/profile/edit/preferences')">
|
<button @click="$router.push('/profile/edit/preferences')">
|
||||||
@@ -28,11 +30,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import { IUserProfile } from '@/types/user'
|
import { IAuthUserProfile } from '@/types/user'
|
||||||
import { languageLabels } from '@/utils/locales'
|
import { languageLabels } from '@/utils/locales'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: IUserProfile
|
user: IAuthUserProfile
|
||||||
}
|
}
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
@@ -45,4 +47,7 @@
|
|||||||
const timezone = computed(() =>
|
const timezone = computed(() =>
|
||||||
props.user.timezone ? props.user.timezone : 'Europe/Paris'
|
props.user.timezone ? props.user.timezone : 'Europe/Paris'
|
||||||
)
|
)
|
||||||
|
const display_ascent = computed(() =>
|
||||||
|
props.user.display_ascent ? 'DISPLAYED' : 'HIDDEN'
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -23,34 +23,66 @@
|
|||||||
@updateTimezone="updateTZ"
|
@updateTimezone="updateTZ"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label class="form-items">
|
<div class="form-items form-checkboxes">
|
||||||
{{ $t('user.PROFILE.FIRST_DAY_OF_WEEK') }}
|
<span class="checkboxes-label">
|
||||||
<select id="weekm" v-model="userForm.weekm" :disabled="loading">
|
{{ $t('user.PROFILE.FIRST_DAY_OF_WEEK') }}
|
||||||
<option
|
</span>
|
||||||
v-for="start in weekStart"
|
<div class="checkboxes">
|
||||||
:value="start.value"
|
<label v-for="start in weekStart" :key="start.label">
|
||||||
:key="start.value"
|
<input
|
||||||
>
|
type="radio"
|
||||||
{{ $t(`user.PROFILE.${start.label}`) }}
|
:id="start.label"
|
||||||
</option>
|
:name="start.label"
|
||||||
</select>
|
:checked="start.value === userForm.weekm"
|
||||||
</label>
|
:disabled="loading"
|
||||||
<label class="form-items">
|
@input="updateWeekM(start.value)"
|
||||||
{{ $t('user.PROFILE.UNITS.LABEL') }}
|
/>
|
||||||
<select
|
<span class="checkbox-label">
|
||||||
id="imperial_units"
|
{{ $t(`user.PROFILE.${start.label}`) }}
|
||||||
v-model="userForm.imperial_units"
|
</span>
|
||||||
:disabled="loading"
|
</label>
|
||||||
>
|
</div>
|
||||||
<option
|
</div>
|
||||||
v-for="unit in imperialUnits"
|
<div class="form-items form-checkboxes">
|
||||||
:value="unit.value"
|
<span class="checkboxes-label">
|
||||||
:key="unit.value"
|
{{ $t('user.PROFILE.UNITS.LABEL') }}
|
||||||
>
|
</span>
|
||||||
{{ $t(`user.PROFILE.UNITS.${unit.label}`) }}
|
<div class="checkboxes">
|
||||||
</option>
|
<label v-for="unit in imperialUnits" :key="unit.label">
|
||||||
</select>
|
<input
|
||||||
</label>
|
type="radio"
|
||||||
|
:id="unit.label"
|
||||||
|
:name="unit.label"
|
||||||
|
:checked="unit.value === userForm.imperial_units"
|
||||||
|
:disabled="loading"
|
||||||
|
@input="updateImperialUnit(unit.value)"
|
||||||
|
/>
|
||||||
|
<span class="checkbox-label">
|
||||||
|
{{ $t(`user.PROFILE.UNITS.${unit.label}`) }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-items form-checkboxes">
|
||||||
|
<span class="checkboxes-label">
|
||||||
|
{{ $t('user.PROFILE.ASCENT_DATA') }}
|
||||||
|
</span>
|
||||||
|
<div class="checkboxes">
|
||||||
|
<label v-for="status in ascentData" :key="status.label">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
:id="status.label"
|
||||||
|
:name="status.label"
|
||||||
|
:checked="status.value === userForm.display_ascent"
|
||||||
|
:disabled="loading"
|
||||||
|
@input="updateAscentDisplay(status.value)"
|
||||||
|
/>
|
||||||
|
<span class="checkbox-label">
|
||||||
|
{{ $t(`common.${status.label}`) }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-buttons">
|
<div class="form-buttons">
|
||||||
<button class="confirm" type="submit">
|
<button class="confirm" type="submit">
|
||||||
{{ $t('buttons.SUBMIT') }}
|
{{ $t('buttons.SUBMIT') }}
|
||||||
@@ -72,40 +104,51 @@
|
|||||||
|
|
||||||
import TimezoneDropdown from '@/components/User/ProfileEdition/TimezoneDropdown.vue'
|
import TimezoneDropdown from '@/components/User/ProfileEdition/TimezoneDropdown.vue'
|
||||||
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
import { AUTH_USER_STORE, ROOT_STORE } from '@/store/constants'
|
||||||
import { IUserProfile, IUserPreferencesPayload } from '@/types/user'
|
import { IUserPreferencesPayload, IAuthUserProfile } from '@/types/user'
|
||||||
import { useStore } from '@/use/useStore'
|
import { useStore } from '@/use/useStore'
|
||||||
import { availableLanguages } from '@/utils/locales'
|
import { availableLanguages } from '@/utils/locales'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: IUserProfile
|
user: IAuthUserProfile
|
||||||
}
|
}
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
const userForm: IUserPreferencesPayload = reactive({
|
const userForm: IUserPreferencesPayload = reactive({
|
||||||
|
display_ascent: true,
|
||||||
imperial_units: false,
|
imperial_units: false,
|
||||||
language: '',
|
language: '',
|
||||||
timezone: 'Europe/Paris',
|
timezone: 'Europe/Paris',
|
||||||
weekm: false,
|
weekm: false,
|
||||||
})
|
})
|
||||||
const weekStart = [
|
const weekStart = [
|
||||||
{
|
|
||||||
label: 'MONDAY',
|
|
||||||
value: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'SUNDAY',
|
label: 'SUNDAY',
|
||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'MONDAY',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
const imperialUnits = [
|
const imperialUnits = [
|
||||||
|
{
|
||||||
|
label: 'METRIC',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'IMPERIAL',
|
label: 'IMPERIAL',
|
||||||
value: true,
|
value: true,
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
const ascentData = [
|
||||||
{
|
{
|
||||||
label: 'METRIC',
|
label: 'DISPLAYED',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'HIDDEN',
|
||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -122,7 +165,8 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function updateUserForm(user: IUserProfile) {
|
function updateUserForm(user: IAuthUserProfile) {
|
||||||
|
userForm.display_ascent = user.display_ascent
|
||||||
userForm.imperial_units = user.imperial_units ? user.imperial_units : false
|
userForm.imperial_units = user.imperial_units ? user.imperial_units : false
|
||||||
userForm.language = user.language ? user.language : 'en'
|
userForm.language = user.language ? user.language : 'en'
|
||||||
userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris'
|
userForm.timezone = user.timezone ? user.timezone : 'Europe/Paris'
|
||||||
@@ -134,8 +178,43 @@
|
|||||||
function updateTZ(value: string) {
|
function updateTZ(value: string) {
|
||||||
userForm.timezone = value
|
userForm.timezone = value
|
||||||
}
|
}
|
||||||
|
function updateAscentDisplay(value: boolean) {
|
||||||
|
userForm.display_ascent = value
|
||||||
|
}
|
||||||
|
function updateImperialUnit(value: boolean) {
|
||||||
|
userForm.imperial_units = value
|
||||||
|
}
|
||||||
|
function updateWeekM(value: boolean) {
|
||||||
|
userForm.weekm = value
|
||||||
|
}
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
store.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~@/scss/vars.scss';
|
||||||
|
#user-preferences-edition {
|
||||||
|
.form-items {
|
||||||
|
padding-top: $default-padding * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-checkboxes {
|
||||||
|
.checkboxes-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.checkboxes {
|
||||||
|
display: flex;
|
||||||
|
gap: $default-padding;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
.checkbox-label {
|
||||||
|
padding-left: $default-padding * 0.5;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
"CONFIRMATION": "Confirmation",
|
"CONFIRMATION": "Confirmation",
|
||||||
"CONTACT": "contact",
|
"CONTACT": "contact",
|
||||||
"DAY": "day | days",
|
"DAY": "day | days",
|
||||||
|
"DISPLAYED": "Displayed",
|
||||||
"DOCUMENTATION": "documentation",
|
"DOCUMENTATION": "documentation",
|
||||||
"HOME": "Home",
|
"HOME": "Home",
|
||||||
"HERE": "here",
|
"HERE": "here",
|
||||||
|
"HIDDEN": "Hidden",
|
||||||
"SELECTS": {
|
"SELECTS": {
|
||||||
"ORDER_BY": {
|
"ORDER_BY": {
|
||||||
"LABEL": "order by"
|
"LABEL": "order by"
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"PASSWORD_UPDATED": "Your password have been updated. Click {0} to log in.",
|
"PASSWORD_UPDATED": "Your password have been updated. Click {0} to log in.",
|
||||||
"PROFILE": {
|
"PROFILE": {
|
||||||
"ACCOUNT_EDITION": "Account edition",
|
"ACCOUNT_EDITION": "Account edition",
|
||||||
|
"ASCENT_DATA": "Ascent-related data (records, total)",
|
||||||
"BACK_TO_PROFILE": "Back to profile",
|
"BACK_TO_PROFILE": "Back to profile",
|
||||||
"BIO": "Bio",
|
"BIO": "Bio",
|
||||||
"BIRTH_DATE": "Birth date",
|
"BIRTH_DATE": "Birth date",
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
"RECORD": "record | records",
|
"RECORD": "record | records",
|
||||||
"RECORD_AS": "Ave. speed",
|
"RECORD_AS": "Ave. speed",
|
||||||
"RECORD_FD": "Farthest distance",
|
"RECORD_FD": "Farthest distance",
|
||||||
|
"RECORD_HA": "Highest ascent",
|
||||||
"RECORD_LD": "Longest duration",
|
"RECORD_LD": "Longest duration",
|
||||||
"RECORD_MS": "Max. speed",
|
"RECORD_MS": "Max. speed",
|
||||||
"REMAINING_CHARS": "remaining characters",
|
"REMAINING_CHARS": "remaining characters",
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
"CONFIRMATION": "Confirmation",
|
"CONFIRMATION": "Confirmation",
|
||||||
"CONTACT": "contact",
|
"CONTACT": "contact",
|
||||||
"DAY": "jour | jours",
|
"DAY": "jour | jours",
|
||||||
|
"DISPLAYED": "Affiché",
|
||||||
"DOCUMENTATION": "documentation (en)",
|
"DOCUMENTATION": "documentation (en)",
|
||||||
"HOME": "Accueil",
|
"HOME": "Accueil",
|
||||||
"HERE": "ici",
|
"HERE": "ici",
|
||||||
|
"HIDDEN": "Masqué",
|
||||||
"SELECTS": {
|
"SELECTS": {
|
||||||
"ORDER_BY": {
|
"ORDER_BY": {
|
||||||
"LABEL": "trier par "
|
"LABEL": "trier par "
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"PASSWORD_UPDATED": "Votre mot de passe a été mis à jour. Cliquez {0} pour vous connecter.",
|
"PASSWORD_UPDATED": "Votre mot de passe a été mis à jour. Cliquez {0} pour vous connecter.",
|
||||||
"PROFILE": {
|
"PROFILE": {
|
||||||
"ACCOUNT_EDITION": "Mise à jour du compte",
|
"ACCOUNT_EDITION": "Mise à jour du compte",
|
||||||
|
"ASCENT_DATA": "Données relatives au dénivelé positif (records, total)",
|
||||||
"BACK_TO_PROFILE": "Revenir au profil",
|
"BACK_TO_PROFILE": "Revenir au profil",
|
||||||
"BIO": "Bio",
|
"BIO": "Bio",
|
||||||
"BIRTH_DATE": "Date de naissance",
|
"BIRTH_DATE": "Date de naissance",
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
"RECORD": "record | records",
|
"RECORD": "record | records",
|
||||||
"RECORD_AS": "Vitesse moy.",
|
"RECORD_AS": "Vitesse moy.",
|
||||||
"RECORD_FD": "Distance la + longue",
|
"RECORD_FD": "Distance la + longue",
|
||||||
|
"RECORD_HA": "Dénivelé positif le + élevé",
|
||||||
"RECORD_LD": "Durée la + longue",
|
"RECORD_LD": "Durée la + longue",
|
||||||
"RECORD_MS": "Vitesse max.",
|
"RECORD_MS": "Vitesse max.",
|
||||||
"REMAINING_CHARS": "nombre de caractères restants ",
|
"REMAINING_CHARS": "nombre de caractères restants ",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface IUserProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IAuthUserProfile extends IUserProfile {
|
export interface IAuthUserProfile extends IUserProfile {
|
||||||
|
display_ascent: boolean
|
||||||
imperial_units: boolean
|
imperial_units: boolean
|
||||||
language: string | null
|
language: string | null
|
||||||
timezone: string
|
timezone: string
|
||||||
@@ -58,6 +59,7 @@ export interface IAdminUserPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IUserPreferencesPayload {
|
export interface IUserPreferencesPayload {
|
||||||
|
display_ascent: boolean
|
||||||
imperial_units: boolean
|
imperial_units: boolean
|
||||||
language: string
|
language: string
|
||||||
timezone: string
|
timezone: string
|
||||||
|
|||||||
@@ -9,30 +9,47 @@ export const formatRecord = (
|
|||||||
tz: string,
|
tz: string,
|
||||||
useImperialUnits: boolean
|
useImperialUnits: boolean
|
||||||
): Record<string, string | number> => {
|
): Record<string, string | number> => {
|
||||||
const unitFrom: TUnit = 'km'
|
const distanceUnitFrom: TUnit = 'km'
|
||||||
const unitTo: TUnit = useImperialUnits
|
const distanceUnitTo: TUnit = useImperialUnits
|
||||||
? units[unitFrom].defaultTarget
|
? units[distanceUnitFrom].defaultTarget
|
||||||
: unitFrom
|
: distanceUnitFrom
|
||||||
|
const ascentUnitFrom: TUnit = 'm'
|
||||||
|
const ascentUnitTo: TUnit = useImperialUnits
|
||||||
|
? units[ascentUnitFrom].defaultTarget
|
||||||
|
: ascentUnitFrom
|
||||||
let value
|
let value
|
||||||
switch (record.record_type) {
|
switch (record.record_type) {
|
||||||
case 'AS':
|
case 'AS':
|
||||||
case 'MS':
|
case 'MS':
|
||||||
value = `${convertDistance(
|
value = `${convertDistance(
|
||||||
+record.value,
|
+record.value,
|
||||||
unitFrom,
|
distanceUnitFrom,
|
||||||
unitTo,
|
distanceUnitTo,
|
||||||
2
|
2
|
||||||
)} ${unitTo}/h`
|
)} ${distanceUnitTo}/h`
|
||||||
break
|
break
|
||||||
case 'FD':
|
case 'FD':
|
||||||
value = `${convertDistance(+record.value, unitFrom, unitTo, 3)} ${unitTo}`
|
value = `${convertDistance(
|
||||||
|
+record.value,
|
||||||
|
distanceUnitFrom,
|
||||||
|
distanceUnitTo,
|
||||||
|
3
|
||||||
|
)} ${distanceUnitTo}`
|
||||||
|
break
|
||||||
|
case 'HA':
|
||||||
|
value = `${convertDistance(
|
||||||
|
+record.value,
|
||||||
|
ascentUnitFrom,
|
||||||
|
ascentUnitTo,
|
||||||
|
2
|
||||||
|
)} ${ascentUnitTo}`
|
||||||
break
|
break
|
||||||
case 'LD':
|
case 'LD':
|
||||||
value = record.value
|
value = record.value
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid record type, expected: "AS", "FD", "LD", "MD", got: "${record.record_type}"`
|
`Invalid record type, expected: "AS", "FD", "HA", "LD", "MD", got: "${record.record_type}"`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -55,21 +72,24 @@ export const getRecordsBySports = (
|
|||||||
records: IRecord[],
|
records: IRecord[],
|
||||||
translatedSports: ITranslatedSport[],
|
translatedSports: ITranslatedSport[],
|
||||||
tz: string,
|
tz: string,
|
||||||
useImperialUnits: boolean
|
useImperialUnits: boolean,
|
||||||
|
display_ascent: boolean
|
||||||
): IRecordsBySports =>
|
): IRecordsBySports =>
|
||||||
records.reduce((sportList: IRecordsBySports, record) => {
|
records
|
||||||
const sport = translatedSports.find((s) => s.id === record.sport_id)
|
.filter((r) => (display_ascent ? true : r.record_type !== 'HA'))
|
||||||
if (sport && sport.label) {
|
.reduce((sportList: IRecordsBySports, record) => {
|
||||||
if (sportList[sport.translatedLabel] === void 0) {
|
const sport = translatedSports.find((s) => s.id === record.sport_id)
|
||||||
sportList[sport.translatedLabel] = {
|
if (sport && sport.label) {
|
||||||
label: sport.label,
|
if (sportList[sport.translatedLabel] === void 0) {
|
||||||
color: sport.color,
|
sportList[sport.translatedLabel] = {
|
||||||
records: [],
|
label: sport.label,
|
||||||
|
color: sport.color,
|
||||||
|
records: [],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
sportList[sport.translatedLabel].records.push(
|
||||||
|
formatRecord(record, tz, useImperialUnits)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
sportList[sport.translatedLabel].records.push(
|
return sportList
|
||||||
formatRecord(record, tz, useImperialUnits)
|
}, {})
|
||||||
)
|
|
||||||
}
|
|
||||||
return sportList
|
|
||||||
}, {})
|
|
||||||
|
|||||||
@@ -94,6 +94,28 @@ describe('formatRecord', () => {
|
|||||||
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "return formatted record for 'Highest ascent'",
|
||||||
|
inputParams: {
|
||||||
|
record: {
|
||||||
|
id: 13,
|
||||||
|
record_type: 'HA',
|
||||||
|
sport_id: 1,
|
||||||
|
user: 'admin',
|
||||||
|
value: 100,
|
||||||
|
workout_date: 'Sun, 07 Jul 2019 08:00:00 GMT',
|
||||||
|
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||||
|
},
|
||||||
|
timezone: 'Europe/Paris',
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
id: 13,
|
||||||
|
record_type: 'HA',
|
||||||
|
value: '100 m',
|
||||||
|
workout_date: '2019/07/07',
|
||||||
|
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
testsParams.map((testParams) => {
|
testsParams.map((testParams) => {
|
||||||
it(testParams.description, () => {
|
it(testParams.description, () => {
|
||||||
@@ -199,6 +221,28 @@ describe('formatRecord after conversion', () => {
|
|||||||
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "return formatted record for 'Highest ascent'",
|
||||||
|
inputParams: {
|
||||||
|
record: {
|
||||||
|
id: 13,
|
||||||
|
record_type: 'HA',
|
||||||
|
sport_id: 1,
|
||||||
|
user: 'admin',
|
||||||
|
value: 100,
|
||||||
|
workout_date: 'Sun, 07 Jul 2019 08:00:00 GMT',
|
||||||
|
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||||
|
},
|
||||||
|
timezone: 'Europe/Paris',
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
id: 13,
|
||||||
|
record_type: 'HA',
|
||||||
|
value: '328.08 ft',
|
||||||
|
workout_date: '2019/07/07',
|
||||||
|
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
testsParams.map((testParams) => {
|
testsParams.map((testParams) => {
|
||||||
it(testParams.description, () => {
|
it(testParams.description, () => {
|
||||||
@@ -231,7 +275,7 @@ describe('formatRecord (invalid record type)', () => {
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
).to.throw(
|
).to.throw(
|
||||||
'Invalid record type, expected: "AS", "FD", "LD", "MD", got: "M"'
|
'Invalid record type, expected: "AS", "FD", "HA", "LD", "MD", got: "M"'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -356,7 +400,8 @@ describe('getRecordsBySports', () => {
|
|||||||
testParams.input.records,
|
testParams.input.records,
|
||||||
translatedSports,
|
translatedSports,
|
||||||
testParams.input.tz,
|
testParams.input.tz,
|
||||||
false
|
false,
|
||||||
|
true
|
||||||
),
|
),
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -486,6 +531,7 @@ describe('getRecordsBySports after conversion', () => {
|
|||||||
testParams.input.records,
|
testParams.input.records,
|
||||||
translatedSports,
|
translatedSports,
|
||||||
testParams.input.tz,
|
testParams.input.tz,
|
||||||
|
true,
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
@@ -495,3 +541,73 @@ describe('getRecordsBySports after conversion', () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('getRecordsBySports with HA record', () => {
|
||||||
|
const testsParams = [
|
||||||
|
{
|
||||||
|
description: 'returns empty object if no records',
|
||||||
|
input: {
|
||||||
|
records: [],
|
||||||
|
tz: 'Europe/Paris',
|
||||||
|
},
|
||||||
|
expected: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns records except HA record',
|
||||||
|
input: {
|
||||||
|
records: [
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
record_type: 'AS',
|
||||||
|
sport_id: 1,
|
||||||
|
user: 'admin',
|
||||||
|
value: 18,
|
||||||
|
workout_date: 'Sun, 07 Jul 2019 08:00:00 GMT',
|
||||||
|
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
record_type: 'HA',
|
||||||
|
sport_id: 1,
|
||||||
|
user: 'admin',
|
||||||
|
value: 235,
|
||||||
|
workout_date: 'Sun, 07 Jul 2019 08:00:00 GMT',
|
||||||
|
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tz: 'Europe/Paris',
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
'Cycling (Sport)': {
|
||||||
|
color: null,
|
||||||
|
label: 'Cycling (Sport)',
|
||||||
|
records: [
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
record_type: 'AS',
|
||||||
|
value: '18 km/h',
|
||||||
|
workout_date: '2019/07/07',
|
||||||
|
workout_id: 'hvYBqYBRa7wwXpaStWR4V2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
testsParams.map((testParams) =>
|
||||||
|
it(testParams.description, () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
getRecordsBySports(
|
||||||
|
testParams.input.records,
|
||||||
|
translatedSports,
|
||||||
|
testParams.input.tz,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
testParams.expected
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user