Skip to content

Add IG Task Activity Summary API with date filtering and top contributors#2676

Open
joshnajojo12 wants to merge 3 commits into
gtech-mulearn:devfrom
joshnajojo12:ig-task-summary-api
Open

Add IG Task Activity Summary API with date filtering and top contributors#2676
joshnajojo12 wants to merge 3 commits into
gtech-mulearn:devfrom
joshnajojo12:ig-task-summary-api

Conversation

@joshnajojo12

Copy link
Copy Markdown
Contributor

Fixes #2658

Implementation

Added IGTaskSummaryAPI endpoint to return task activity summary for a given Interest Group.

The API calculates:

  • total tasks completed
  • total karma awarded
  • unique contributors
  • top 5 contributors by karma

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.

Screenshot 2026-03-18 225331

@awindsr

awindsr commented Jun 22, 2026

Copy link
Copy Markdown
Member

@greptileai

@greptile-apps

greptile-apps Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds the IGTaskSummaryAPI endpoint for task activity summaries on Interest Groups, along with several unrelated new endpoints: CampusProgramParticipationAPI, KarmaHistogramAPI, StudentParticipationBreakdownAPI, and three Launchpad job analytics APIs. It also fixes college sort fields.

  • IGTaskSummaryAPI (GET /ig/<ig_id>/task-summary/) aggregates total tasks, karma, unique contributors, and top-5 contributors from KarmaActivityLog with optional date filtering — but the URL is registered with a duplicate ig/ prefix, and the query lacks an appraiser_approved=True filter, meaning pending and rejected submissions inflate the reported totals.
  • StudentParticipationBreakdownAPI returns the full student list via list(queryset) without any pagination, which will be a memory and latency concern as the platform grows.
  • The three Launchpad analytics endpoints (JobAnalyticsAPI, JobTrendsAnalyticsAPI, JobsSummaryAnalyticsAPI) are cleanly implemented with proper company-ownership checks.

Confidence Score: 3/5

Not safe to merge without fixing the URL routing and the missing approval filter — both cause the primary new endpoint to either be unreachable or return incorrect data.

The core endpoint added by this PR is broken in two ways: its URL is double-prefixed so it cannot be reached at the advertised path, and its aggregation query counts unapproved/rejected task submissions as 'completed', producing inflated metrics. These are present defects in the changed code, not theoretical concerns.

api/dashboard/ig/urls.py and api/dashboard/ig/dash_ig_view.py need the most attention — the URL prefix error and missing approval filter must both be resolved before the endpoint works correctly.

Important Files Changed

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 }"
Loading
%%{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 }"
Loading

Reviews (1): Last reviewed commit: "Add dashboard and launchpad analytics AP..." | Re-trigger Greptile

Comment thread api/dashboard/ig/urls.py
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()),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 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/).

Suggested change
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'),

Comment on lines +388 to +392
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 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.

Suggested change
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)

Comment on lines +62 to +65
return CustomResponse(
response={"students": list(students)},
general_message="Student participation breakdown fetched successfully",
).get_success_response()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 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.

Suggested change
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()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement: Interest Group Task Activity Summary API

2 participants