diff --git a/backend/models/dtos/stats_dto.py b/backend/models/dtos/stats_dto.py index 3cae0fec92..7ce93c1d7b 100644 --- a/backend/models/dtos/stats_dto.py +++ b/backend/models/dtos/stats_dto.py @@ -12,9 +12,6 @@ class UserContribution(Model): picture_url = StringType(serialized_name="pictureUrl") mapped = IntType() validated = IntType() - split = IntType() - marked_bad_imagery = IntType() - invalidated = IntType() total = IntType() mapped_tasks = ListType(IntType, serialized_name="mappedTasks") validated_tasks = ListType(IntType, serialized_name="validatedTasks") diff --git a/backend/services/stats_service.py b/backend/services/stats_service.py index 37c165068b..6e5707e3cb 100644 --- a/backend/services/stats_service.py +++ b/backend/services/stats_service.py @@ -247,29 +247,24 @@ def get_last_activity(project_id: int) -> ProjectLastActivityDTO: def get_user_contributions(project_id: int) -> ProjectContributionsDTO: """ Get all user contributions on a project""" - actions = [ - TaskStatus.MAPPED.name, - TaskStatus.BADIMAGERY.name, - TaskStatus.SPLIT.name, - TaskStatus.VALIDATED.name, - TaskStatus.INVALIDATED.name, - ] - - # Filter for rows with the actions in a task's possible state_changes. - sq = ( - TaskHistory.query.with_entities( - TaskHistory.user_id, - TaskHistory.task_id, - TaskHistory.action_text, - ) - .distinct( - TaskHistory.task_id, - TaskHistory.action_text, + mapped_stmt = ( + Task.query.with_entities( + Task.mapped_by, + func.count(Task.mapped_by).label("count"), + func.array_agg(Task.id).label("task_ids"), ) - .filter( - TaskHistory.action_text.in_(actions), - TaskHistory.project_id == project_id, + .filter(Task.project_id == project_id) + .group_by(Task.mapped_by) + .subquery() + ) + validated_stmt = ( + Task.query.with_entities( + Task.validated_by, + func.count(Task.validated_by).label("count"), + func.array_agg(Task.id).label("task_ids"), ) + .filter(Task.project_id == project_id) + .group_by(Task.validated_by) .subquery() ) @@ -281,46 +276,27 @@ def get_user_contributions(project_id: int) -> ProjectContributionsDTO: User.mapping_level, User.picture_url, User.date_registered, - func.count(sq.c.action_text) - .filter(sq.c.action_text == TaskStatus.MAPPED.name) - .label("mapped"), - func.count(sq.c.action_text) - .filter( - sq.c.action_text == TaskStatus.BADIMAGERY.name, - ) - .label("bad_imagery"), - func.count(sq.c.action_text) - .filter(sq.c.action_text == TaskStatus.SPLIT.name) - .label("split"), - func.count(sq.c.action_text) - .filter(sq.c.action_text == TaskStatus.VALIDATED.name) - .label("validated"), - func.count(sq.c.action_text) - .filter(sq.c.action_text == TaskStatus.INVALIDATED.name) - .label("invalidated"), - func.array_agg(sq.c.task_id) - .filter(sq.c.action_text == TaskStatus.MAPPED.name) - .label("mapped_tasks"), - func.array_agg(sq.c.task_id) - .filter(sq.c.action_text == TaskStatus.VALIDATED.name) - .label("validated_tasks"), + coalesce(mapped_stmt.c.count, 0).label("mapped"), + coalesce(validated_stmt.c.count, 0).label("validated"), ( - coalesce( - func.count(sq.c.action_text).filter( - sq.c.action_text == TaskStatus.MAPPED.name - ), - 0, - ) - + coalesce( - func.count(sq.c.action_text).filter( - sq.c.action_text == TaskStatus.VALIDATED.name - ), - 0, - ) + coalesce(mapped_stmt.c.count, 0) + + coalesce(validated_stmt.c.count, 0) ).label("total"), + mapped_stmt.c.task_ids.label("mapped_tasks"), + validated_stmt.c.task_ids.label("validated_tasks"), + ) + .outerjoin( + validated_stmt, + mapped_stmt.c.mapped_by == validated_stmt.c.validated_by, + full=True, + ) + .join( + User, + or_( + User.id == mapped_stmt.c.mapped_by, + User.id == validated_stmt.c.validated_by, + ), ) - .join(User, sq.c.user_id == User.id) - .group_by(User.id) .order_by(desc("total")) .all() ) @@ -335,9 +311,6 @@ def get_user_contributions(project_id: int) -> ProjectContributionsDTO: picture_url=r.picture_url, mapped=r.mapped, validated=r.validated, - split=r.split, - marked_bad_imagery=r.bad_imagery, - invalidated=r.invalidated, total=r.total, mapped_tasks=r.mapped_tasks if r.mapped_tasks is not None else [], validated_tasks=r.validated_tasks diff --git a/tests/backend/integration/api/projects/test_contributions.py b/tests/backend/integration/api/projects/test_contributions.py index 0c1a8fbfb6..42eaa036d2 100644 --- a/tests/backend/integration/api/projects/test_contributions.py +++ b/tests/backend/integration/api/projects/test_contributions.py @@ -18,18 +18,7 @@ def test_returns_404_if_project_does_not_exist(self): self.assertEqual(response.status_code, 404) def test_returns_200_if_project_exists(self): - # Arrange - task = Task.get(2, self.test_project.id) - # Lock the task for mapping - task.lock_task_for_mapping(self.test_author.id) - # Unlock the task - task.unlock_task(self.test_author.id, new_state=TaskStatus.MAPPED) - task_2 = Task.get(1, self.test_project.id) - # Lock the task for mapping - task_2.lock_task_for_validating(self.test_author.id) - # Unlock the task - task_2.unlock_task(self.test_author.id, new_state=TaskStatus.VALIDATED) - # Actt + # Act response = self.client.get(self.url) # Assert self.assertEqual(response.status_code, 200) @@ -50,17 +39,14 @@ def test_returns_200_if_project_exists(self): "validatedTasks", "name", "dateRegistered", - "invalidated", - "split", - "marked_bad_imagery", ] ), ) self.assertEqual(test_user_contribution["username"], self.test_author.username) - self.assertEqual(test_user_contribution["mapped"], 1) + self.assertEqual(test_user_contribution["mapped"], 3) self.assertEqual(test_user_contribution["validated"], 1) - self.assertEqual(test_user_contribution["mappedTasks"], [2]) - self.assertEqual(test_user_contribution["validatedTasks"], [1]) + self.assertEqual(test_user_contribution["mappedTasks"], [1, 3, 4]) + self.assertEqual(test_user_contribution["validatedTasks"], [4]) class TestProjectsContributionsQueriesDayAPI(BaseTestCase):