Add IG Task Activity Summary API with date filtering and top contributors#2676
Add IG Task Activity Summary API with date filtering and top contributors#2676joshnajojo12 wants to merge 3 commits into
Conversation
Greptile SummaryThis PR adds the
|
| Filename | Overview |
|---|---|
| api/dashboard/ig/urls.py | Registers the new IGTaskSummaryAPI route with a redundant ig/ prefix — the actual URL resolves to /ig/ig/<id>/task-summary/ instead of the intended /ig/<id>/task-summary/. |
| api/dashboard/ig/dash_ig_view.py | Adds IGTaskSummaryAPI with date-filtered aggregation over KarmaActivityLog; omits appraiser_approved=True filter so pending/rejected submissions inflate "tasks completed" and "karma awarded" counts. |
| api/dashboard/ig/dash_ig_serializer.py | Adds IGTopContributorSerializer and IGTaskSummarySerializer; straightforward DRF Serializer definitions with no issues. |
| api/dashboard/student/student_views.py | New StudentParticipationBreakdownAPI uses subquery annotations correctly but returns the entire student queryset via list() with no pagination, which will cause memory and latency issues at scale. |
| api/dashboard/karma/karma_views.py | New KarmaHistogramAPI correctly buckets student karma using Floor/Case annotations and fills empty buckets; logic and edge cases look sound. |
| api/launchpad/launchpad_views.py | Adds JobAnalyticsAPI, JobTrendsAnalyticsAPI, and JobsSummaryAnalyticsAPI with proper company-ownership checks and date filtering; logic is clean with no critical issues. |
| api/dashboard/campus/campus_views.py | Adds CampusProgramParticipationAPI that annotates a campus org with learning-circle program and participant counts; role check and null guards are correct. |
| api/dashboard/college/college_view.py | Fixes college sort_fields — org now maps to org__title for proper title sorting, and adds level and created_at sort options. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Client
participant IGTaskSummaryAPI
participant InterestGroup DB
participant KarmaActivityLog DB
Client->>IGTaskSummaryAPI: "GET /ig/<ig_id>/task-summary/?from_date&to_date"
IGTaskSummaryAPI->>IGTaskSummaryAPI: "Validate JWT & role (ADMIN/FELLOW/ASSOCIATE)"
IGTaskSummaryAPI->>InterestGroup DB: Filter by ig_id → get id, name, code
InterestGroup DB-->>IGTaskSummaryAPI: ig or None
alt IG not found
IGTaskSummaryAPI-->>Client: 404 Not Found
end
IGTaskSummaryAPI->>IGTaskSummaryAPI: "Parse & validate from_date / to_date"
alt Invalid date format
IGTaskSummaryAPI-->>Client: 400 Invalid date format
end
IGTaskSummaryAPI->>KarmaActivityLog DB: aggregate(total_tasks, total_karma, unique_contributors) filter: task__ig_id=ig_id [+ date range]
KarmaActivityLog DB-->>IGTaskSummaryAPI: summary_values
IGTaskSummaryAPI->>KarmaActivityLog DB: values(user__full_name, user__muid) annotate(karma_earned=Sum(karma)) order_by(-karma_earned)[:5]
KarmaActivityLog DB-->>IGTaskSummaryAPI: top_contributors (up to 5)
IGTaskSummaryAPI->>IGTaskSummaryAPI: Serialize with IGTaskSummarySerializer
IGTaskSummaryAPI-->>Client: "200 { ig_id, ig_name, total_tasks_completed, total_karma_awarded, unique_contributors, top_contributors, date_range }"
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Client
participant IGTaskSummaryAPI
participant InterestGroup DB
participant KarmaActivityLog DB
Client->>IGTaskSummaryAPI: "GET /ig/<ig_id>/task-summary/?from_date&to_date"
IGTaskSummaryAPI->>IGTaskSummaryAPI: "Validate JWT & role (ADMIN/FELLOW/ASSOCIATE)"
IGTaskSummaryAPI->>InterestGroup DB: Filter by ig_id → get id, name, code
InterestGroup DB-->>IGTaskSummaryAPI: ig or None
alt IG not found
IGTaskSummaryAPI-->>Client: 404 Not Found
end
IGTaskSummaryAPI->>IGTaskSummaryAPI: "Parse & validate from_date / to_date"
alt Invalid date format
IGTaskSummaryAPI-->>Client: 400 Invalid date format
end
IGTaskSummaryAPI->>KarmaActivityLog DB: aggregate(total_tasks, total_karma, unique_contributors) filter: task__ig_id=ig_id [+ date range]
KarmaActivityLog DB-->>IGTaskSummaryAPI: summary_values
IGTaskSummaryAPI->>KarmaActivityLog DB: values(user__full_name, user__muid) annotate(karma_earned=Sum(karma)) order_by(-karma_earned)[:5]
KarmaActivityLog DB-->>IGTaskSummaryAPI: top_contributors (up to 5)
IGTaskSummaryAPI->>IGTaskSummaryAPI: Serialize with IGTaskSummarySerializer
IGTaskSummaryAPI-->>Client: "200 { ig_id, ig_name, total_tasks_completed, total_karma_awarded, unique_contributors, top_contributors, date_range }"
Reviews (1): Last reviewed commit: "Add dashboard and launchpad analytics AP..." | Re-trigger Greptile
| path('', dash_ig_view.InterestGroupAPI.as_view()), # for get data and create new interest groups | ||
| path('list/', dash_ig_view.InterestGroupListApi.as_view()), # for public listing without admin permission | ||
| path('csv/', dash_ig_view.InterestGroupCSV.as_view()), # for IG data CSV download | ||
| path('ig/<str:ig_id>/task-summary/', dash_ig_view.IGTaskSummaryAPI.as_view()), |
There was a problem hiding this comment.
The URL path includes a redundant
ig/ segment. The parent api/dashboard/urls.py already mounts this module under path("ig/", ...), so the full registered URL becomes /api/v1/dashboard/ig/ig/<ig_id>/task-summary/ — not the intended /api/v1/dashboard/ig/<ig_id>/task-summary/ shown in the PR description. All other entries in this file omit the ig/ prefix (e.g., <str:pk>/, list/).
| path('ig/<str:ig_id>/task-summary/', dash_ig_view.IGTaskSummaryAPI.as_view()), | |
| path('<str:ig_id>/task-summary/', dash_ig_view.IGTaskSummaryAPI.as_view(), name='ig-task-summary'), |
| filters = Q(task__ig_id=ig_id) | ||
| if from_date: | ||
| filters &= Q(created_at__date__gte=from_date) | ||
| if to_date: | ||
| filters &= Q(created_at__date__lte=to_date) |
There was a problem hiding this comment.
The
KarmaActivityLog model has appraiser_approved and peer_approved boolean fields (both nullable), indicating it stores submissions in all states: pending (None), approved (True), and rejected (False). Querying without an approval filter will count pending and rejected submissions as "tasks completed" and inflate total_karma_awarded with unearned karma. The summary should restrict to approved entries.
| filters = Q(task__ig_id=ig_id) | |
| if from_date: | |
| filters &= Q(created_at__date__gte=from_date) | |
| if to_date: | |
| filters &= Q(created_at__date__lte=to_date) | |
| filters = Q(task__ig_id=ig_id, appraiser_approved=True) | |
| if from_date: | |
| filters &= Q(created_at__date__gte=from_date) | |
| if to_date: | |
| filters &= Q(created_at__date__lte=to_date) |
| return CustomResponse( | ||
| response={"students": list(students)}, | ||
| general_message="Student participation breakdown fetched successfully", | ||
| ).get_success_response() |
There was a problem hiding this comment.
list(students) evaluates the entire User queryset into memory with no pagination. As the number of students grows, this response will become unbounded in size and execution time. The endpoint should use CommonUtils pagination like other listing endpoints in this codebase, or at minimum add a hard LIMIT.
| return CustomResponse( | |
| response={"students": list(students)}, | |
| general_message="Student participation breakdown fetched successfully", | |
| ).get_success_response() | |
| paginated = CommonUtils.paginate_queryset(students, request) | |
| return CustomResponse( | |
| response={"students": list(paginated.get("queryset"))}, | |
| general_message="Student participation breakdown fetched successfully", | |
| ).get_success_response() |
Fixes #2658
Implementation
Added IGTaskSummaryAPI endpoint to return task activity summary for a given Interest Group.
The API calculates:
Aggregation is performed using Django ORM (Count, Sum, Coalesce) on KarmaActivityLog to avoid N+1 queries.
Supports optional date filtering using from_date and to_date query parameters.
Testing
Tested using Postman:
GET /api/v1/dashboard/ig/<ig_id>/task-summary/
GET /api/v1/dashboard/ig/<ig_id>/task-summary/?from_date=2025-01-01&to_date=2025-03-10
Endpoint correctly enforces authentication and returns 403 when token is missing.