Skip to content

Commit

Permalink
GraphQL schema for exercises aggregated by week (#27)
Browse files Browse the repository at this point in the history
* update query to return weekly aggregated exercises

* updated graph ql schema with weekly aggregated stats

* updating rest api views to use  query

* naming the graphql query following best practices

* Adjusting graphql schema totalDistance field to be Float type

* using graphql endpoint to query for weekly journal data

* formatting logger in graphql endpoint

* updated data with new exercise schema fields

* test to check new query

* reverting change in journal

* fix for graphql schema loading on the playground

- includes changes to journal and statistics pages to access the right data as response.data.data (redundant but it is how graphql server returns data)
- updated tests to use new path to access stats
  • Loading branch information
VathsalaAchar authored Mar 29, 2024
1 parent 6e32b50 commit a8aabbd
Show file tree
Hide file tree
Showing 7 changed files with 1,445 additions and 84 deletions.
21 changes: 18 additions & 3 deletions analytics/core/gql_schema.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from ariadne import gql, make_executable_schema, QueryType
from core.models import mongo
from core.query import stats_query, stats_by_username_query

from core.query import stats_query, stats_by_username_query, stats_by_week_start_end_dates_query

type_defs = gql("""
type Query {
stats: [Stats]
statsByUsername(username: String): [Stats]
statsAggregatedByWeek(username:String, startdate: String, enddate: String): [Exercise]
}
type Stats {
Expand All @@ -17,7 +17,7 @@
type Exercise {
exerciseType: ExerciseType!
totalDuration: Int!
totalDistance: Int
totalDistance: Float
averagePace: Float
averageSpeed: Float
topSpeed: Float
Expand All @@ -30,10 +30,12 @@
Gym
Other
}
""")

query = QueryType()


@query.field("stats")
def stats_resolver(*_):
pipeline = stats_query()
Expand All @@ -49,4 +51,17 @@ def stats_by_username_resolver(*_, username=None):
stats = list(mongo.db.exercises.aggregate(pipeline))
return stats


@query.field("statsAggregatedByWeek")
def stats_by_week_start_end_dates_resolver(*_, username=None, startdate=None, enddate=None):
pipeline = stats_by_week_start_end_dates_query(
username=username,
start_date=startdate,
end_date=enddate
)

stats = list(mongo.db.exercises.aggregate(pipeline))
return stats


schema = make_executable_schema(type_defs, query)
46 changes: 46 additions & 0 deletions analytics/core/query.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from datetime import datetime, timedelta


def stats_query():
return [
{
Expand Down Expand Up @@ -79,3 +82,46 @@ def stats_by_username_query(username):
}
}
]


def stats_by_week_start_end_dates_query(username, start_date, end_date):
# format the string dates to a form accepted by mongodb
date_format = "%Y-%m-%d"
start_date = datetime.strptime(start_date, date_format)
# Include the whole end day
end_date = datetime.strptime(end_date, date_format) + timedelta(days=1)

return [
{
"$match": {
"username": username,
"date": {
"$gte": start_date,
"$lt": end_date
}
}
},
{
"$group": {
"_id": {
"exerciseType": "$exerciseType"
},
"totalDuration": {"$sum": "$duration"},
"totalDistance": {"$sum": "$distance"},
"averagePace": {"$avg": "$pace"},
"averageSpeed": {"$avg": "$speed"},
"topSpeed": {"$max": "$speed"}
}
},
{
"$project": {
"exerciseType": "$_id.exerciseType",
"totalDuration": 1,
"totalDistance": 1,
"averagePace": 1,
"averageSpeed": 1,
"topSpeed": 1,
"_id": 0
}
}
]
74 changes: 16 additions & 58 deletions analytics/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,83 +10,43 @@
from ariadne.explorer import ExplorerGraphiQL
from ariadne import graphql_sync

from core.query import stats_query, stats_by_username_query
from core.query import stats_query, stats_by_username_query, stats_by_week_start_end_dates_query

stats_page = Blueprint('stats_page', __name__)


@stats_page.route('/')
def index():
exercises = mongo.db.exercises.find()
exercises_list = list(exercises)
return json_util.dumps(exercises_list)


@stats_page.route('/stats')
def stats():
pipeline = stats_query()

stats = list(mongo.db.exercises.aggregate(pipeline))
return jsonify(stats=stats)


@stats_page.route('/stats/<username>', methods=['GET'])
def user_stats(username):
pipeline = stats_by_username_query(username=username)

stats = list(mongo.db.exercises.aggregate(pipeline))
return jsonify(stats=stats)


@stats_page.route('/stats/weekly/', methods=['GET'])
def weekly_user_stats():
username = request.args.get('user')
start_date_str = request.args.get('start')
end_date_str = request.args.get('end')

date_format = "%Y-%m-%d"
try:
start_date = datetime.strptime(start_date_str, date_format)
# Include the whole end day
end_date = datetime.strptime(
end_date_str, date_format) + timedelta(days=1)
start_date = request.args.get('start')
end_date = request.args.get('end')

logging.info(
f"Fetching weekly stats for user: {username} from {start_date} to {end_date}")
except Exception as e:
logging.error(f"Error parsing dates: {e}")
return jsonify(error="Invalid date format"), 400

pipeline = [
{
"$match": {
"username": username,
"date": {
"$gte": start_date,
"$lt": end_date
}
}
},
{
"$group": {
"_id": {
"exerciseType": "$exerciseType"
},
"totalDuration": {"$sum": "$duration"},
"totalDistance": {"$sum": "$distance"},
"averagePace": {"$avg": "$pace"},
"averageSpeed": {"$avg": "$speed"},
"topSpeed": {"$max": "$speed"}
}
},
{
"$project": {
"exerciseType": "$_id.exerciseType",
"totalDuration": 1,
"totalDistance": 1,
"averagePace": 1,
"averageSpeed": 1,
"topSpeed": 1,
"_id": 0
}
}
]
pipeline = stats_by_week_start_end_dates_query(
username, start_date, end_date
)

try:
stats = list(mongo.db.exercises.aggregate(pipeline))
Expand All @@ -97,23 +57,21 @@ def weekly_user_stats():
traceback.print_exc()
return jsonify(error="An internal error occurred"), 500


explorer_html = ExplorerGraphiQL().html(None)


@stats_page.route('/stats/graphql', methods=["GET"])
def graphql_explorer():
return explorer_html, 200


@stats_page.route("/stats/graphql", methods=["POST"])
def graphql_server():
data = request.get_json()
success, result = graphql_sync(
schema, data, context_value={"request": request}, debug=app.debug
)
app.logger.info('%s data returned:', result)
if success:
data = result['data']
status_code = 200
else:
data = result
status_code = 400
return jsonify(data), status_code
app.logger.info(f'data returned: {result}')
status_code = 200 if success else 400
return jsonify(result), status_code
Loading

0 comments on commit a8aabbd

Please sign in to comment.