Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

Metrics dash board #168

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@ def add_api_endpoints(app):
from backend.api.users.statistics import (
UsersStatisticsAPI,
UsersStatisticsInterestsAPI,
UsersTaskMappedAPI,
UsersTeamStatsAPI,
UserSpecificAPI,
)

# System API endpoint
Expand Down Expand Up @@ -518,7 +521,6 @@ def add_api_endpoints(app):
TasksActionsSplitAPI,
format_url("projects/<int:project_id>/tasks/actions/split/<int:task_id>/"),
)

# Comments REST endoints
api.add_resource(
CommentsProjectsRestAPI,
Expand Down Expand Up @@ -677,6 +679,7 @@ def add_api_endpoints(app):
# Users REST endpoint
api.add_resource(UsersAllAPI, format_url("users/"))
api.add_resource(UsersRestAPI, format_url("users/<int:user_id>/"))

api.add_resource(
UsersQueriesUsernameFilterAPI,
format_url("users/queries/filter/<string:username>/"),
Expand Down Expand Up @@ -712,6 +715,7 @@ def add_api_endpoints(app):
)

api.add_resource(UsersTasksAPI, format_url("users/<int:user_id>/tasks/"))

api.add_resource(
UsersActionsVerifyEmailAPI, format_url("users/me/actions/verify-email/")
)
Expand All @@ -724,6 +728,16 @@ def add_api_endpoints(app):
UsersStatisticsAPI, format_url("users/<string:username>/statistics/")
)

api.add_resource(
UsersTaskMappedAPI, format_url("users/<string:username>/userstaskmapped/")
)

api.add_resource(
UsersTeamStatsAPI, format_url("users/<string:username>/usersteamstats/")
)

api.add_resource(UserSpecificAPI, format_url("users/<string:username>/tasks/"))

# User RecommendedProjects endpoint
api.add_resource(
UsersRecommendedProjectsAPI,
Expand Down
225 changes: 224 additions & 1 deletion backend/api/users/statistics.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from flask_restful import Resource, current_app
from flask_restful import Resource, current_app, request
from backend.services.users.user_service import UserService, NotFound
from backend.services.interests_service import InterestService
from backend.services.users.authentication_service import token_auth
from dateutil.parser import parse as date_parse


class UsersStatisticsAPI(Resource):
Expand All @@ -27,6 +28,7 @@ def get(self, username):
required: true
type: string
default: Thinkwhere

responses:
200:
description: User found
Expand Down Expand Up @@ -87,3 +89,224 @@ def get(self, user_id):
error_msg = f"Interest GET - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": error_msg}, 500


class UsersTaskMappedAPI(Resource):
@token_auth.login_required
def get(self, username):
"""
Get detailed stats about a user by OpenStreetMap username
---
tags:
- users
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- in: path
name: username
description: Mapper's OpenStreetMap username
required: true
type: string
default: null
- in: query
name: project_id
description: Project id
required: false
type: integer
default: null
- in: query
name: start_date
description: Date to filter as minimum
required: false
type: string
default: null
- in: query
name: end_date
description: Date to filter as maximum
required: false
type: string
default: null

responses:
200:
description: User found
401:
description: Unauthorized - Invalid credentials
404:
description: User not found
500:
description: Internal Server Error
"""
try:
project_id = int(request.args.get("project_id", 0))
start_date = (
date_parse(request.args.get("start_date"))
if request.args.get("start_date")
else None
)
end_date = (
date_parse(request.args.get("end_date"))
if request.args.get("end_date")
else None
)
tasks_dto = UserService.get_tasks_mapped(username, start_date=start_date, end_date=end_date, project_id=project_id)

return tasks_dto.to_primitive(), 200
except NotFound:
return {"Error": "UsersTaskMapped not found"}, 404
except Exception as e:
error_msg = f"UsersTaskMapped GET - unhandled error: {str(e)}"
current_app.logger.critical(error_msg)
return {"Error": "Unable to fetch user statistics"}, 500


class UsersTeamStatsAPI(Resource):
@token_auth.login_required
def get(self, username):
"""
Get detailed stats about a user by OpenStreetMap username
---
tags:
- users
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- in: path
name: username
description: Mapper's OpenStreetMap username
required: true
type: string
default: Thinkwhere
- in: query
name: team_id
description: Team Id
required: false
type: integer
default: null
- in: query
name: start_date
description: Date to filter as minimum
required: false
type: string
default: null
- in: query
name: end_date
description: Date to filter as maximum
required: false
type: string
default: null

responses:
200:
description: User found
401:
description: Unauthorized - Invalid credentials
404:
description: User not found
500:
description: Internal Server Error
"""
try:
team_id = int(request.args.get("team_id", 0))
start_date = (
date_parse(request.args.get("start_date"))
if request.args.get("start_date")
else None
)
end_date = (
date_parse(request.args.get("end_date"))
if request.args.get("end_date")
else None
)
teams_dto = UserService.get_teams_stats(username, start_date=start_date, end_date=end_date, team_id=team_id,)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove last ,


return teams_dto.to_primitive(), 200
except NotFound:
return {"Error": "User not found"}, 404
except Exception as e:
error_msg = f"User GET - unhandled error: {str(e)}"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be more specific: UsersTeamStats GET

current_app.logger.critical(error_msg)
return {"Error": "Unable to fetch user statistics"}, 500


class UserSpecificAPI(Resource):
@token_auth.login_required
def get(self, username):
"""
Get a list of tasks a user has interacted with
---
Comment on lines +244 to +249
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be more specific, should the class be named UsersAllTasksAPI ?

tags:
- users
produces:
- application/json
parameters:
- in: header
name: Authorization
description: Base64 encoded session token
required: true
type: string
default: Token sessionTokenHere==
- name: username
in: path
Comment on lines +261 to +262
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

description: Mapper's OpenStreetMap username
required: true
type: string
default: Thinkwhere
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

- in: query
name: status
description: Task Status filter mapped/validated
required: false
type: string
default: null
- in: query
name: start_date
description: Date to filter as minimum
required: false
type: string
default: null
- in: query
name: end_date
description: Date to filter as maximum
required: false
type: string
default: null
responses:
200:
description: Mapped projects found
404:
description: No mapped projects found
Comment on lines +286 to +289
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the descriptions are a little confusing - the docstring of get() says this is for getting a user's tasks but responses here are about projects

500:
description: Internal Server Error
"""
try:
status = request.args.get("status")
start_date = (
date_parse(request.args.get("start_date"))
if request.args.get("start_date")
else None
)
end_date = (
date_parse(request.args.get("end_date"))
if request.args.get("end_date")
else None
)
tasks = UserService.get_user_specific_task(username, task_status=status, start_date=start_date, end_date=end_date,)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my guess: this is for getting a user's tasks across all projects. Will this also include tasks assigned to the user but not marked as "mapped" or "validated" yet? If so, then get_user_tasks_from_all_projects or get_user_overall_stats may be a better name. get_user_specific_task looks confusing when compared to get_tasks_mapped .

return tasks.to_primitive(), 200
except NotFound:
return {"Error": "User or tasks not found"}, 404
except Exception as e:
error_msg = f"User GET - unhandled error: {str(e)}"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserTasksFromAllProjects GET or UserOverallStats GET

current_app.logger.critical(error_msg)
return {"error": error_msg}, 500
21 changes: 19 additions & 2 deletions backend/models/dtos/user_dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class UserDTO(Model):
is_email_verified = EmailType(
serialized_name="isEmailVerified", serialize_when_none=False
)

is_expert = BooleanType(serialized_name="isExpert", serialize_when_none=False)
twitter_id = StringType(serialized_name="twitterId")
facebook_id = StringType(serialized_name="facebookId")
Expand Down Expand Up @@ -125,7 +126,7 @@ class UserContributionDTO(Model):

class UserStatsDTO(Model):
""" DTO containing statistics about the user """

total_time_spent = IntType(serialized_name="totalTimeSpent")
time_spent_mapping = IntType(serialized_name="timeSpentMapping")
time_spent_validating = IntType(serialized_name="timeSpentValidating")
Expand All @@ -145,7 +146,6 @@ class UserStatsDTO(Model):
ModelType(InterestDTO), serialized_name="ContributionsByInterest"
)


class UserOSMDTO(Model):
""" DTO containing OSM details for the user """

Expand Down Expand Up @@ -252,3 +252,20 @@ def __init__(self):

user_tasks = ListType(ModelType(TaskDTO), serialized_name="tasks")
pagination = ModelType(Pagination)

class UserMappedDTO(Model):
""" DTO containing statistics about the user """

task = ListType(IntType(serialized_name="task"))
day = ListType(IntType(serialized_name="day"))

class UserTeamStatsDTO(Model):
""" DTO containing statistics about the teams """

team = ListType(IntType(serialized_name="team"))


class UserSpecificDTO(Model):
""" DTO containing stats about the user """

tasks = ListType(IntType(serialized_name="tasks"))
Loading