-
Notifications
You must be signed in to change notification settings - Fork 96
Add IG Task Activity Summary API with date filtering and top contributors #2676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |||||
| 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()), | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| path('<str:pk>/', dash_ig_view.InterestGroupAPI.as_view()), # for edit and delete | ||||||
| path('get/<str:pk>/', dash_ig_view.InterestGroupGetAPI.as_view()), # for edit and delete | ||||||
| ] | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| from django.db.models import Case, Count, F, IntegerField, Value, When | ||
| from django.db.models.functions import Coalesce, Floor | ||
| from rest_framework.views import APIView | ||
|
|
||
| from db.user import User | ||
| from utils.permission import CustomizePermission, role_required | ||
| from utils.response import CustomResponse | ||
| from utils.types import RoleType | ||
|
|
||
|
|
||
| class KarmaHistogramAPI(APIView): | ||
| authentication_classes = [CustomizePermission] | ||
|
|
||
| @role_required([RoleType.ADMIN.value, RoleType.FELLOW.value, RoleType.ASSOCIATE.value]) | ||
| def get(self, request): | ||
| student_karma_queryset = ( | ||
| User.objects.filter(user_role_link_user__role__title=RoleType.STUDENT.value) | ||
| .distinct() | ||
| .annotate(karma_value=Coalesce("wallet_user__karma", Value(0))) | ||
| .annotate( | ||
| karma_bucket=Case( | ||
| When(karma_value__lte=100, then=Value(0)), | ||
| default=Floor((F("karma_value") - Value(1)) / Value(100)), | ||
| output_field=IntegerField(), | ||
| ) | ||
| ) | ||
| .values("karma_bucket") | ||
| .annotate(users=Count("id")) | ||
| .order_by("karma_bucket") | ||
| ) | ||
|
|
||
| bucket_counts = { | ||
| entry["karma_bucket"]: entry["users"] for entry in student_karma_queryset | ||
| } | ||
|
|
||
| if bucket_counts: | ||
| max_bucket = max(bucket_counts.keys()) | ||
| ranges = [] | ||
| for bucket in range(max_bucket + 1): | ||
| if bucket == 0: | ||
| range_label = "0-100" | ||
| else: | ||
| range_label = f"{bucket * 100 + 1}-{(bucket + 1) * 100}" | ||
|
|
||
| ranges.append( | ||
| { | ||
| "range": range_label, | ||
| "users": bucket_counts.get(bucket, 0), | ||
| } | ||
| ) | ||
| else: | ||
| ranges = [] | ||
|
|
||
| return CustomResponse( | ||
| response={"ranges": ranges}, | ||
| general_message="Karma histogram fetched successfully", | ||
| ).get_success_response() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| from django.urls import path | ||
|
|
||
| from . import karma_views | ||
|
|
||
| urlpatterns = [ | ||
| path("histogram/", karma_views.KarmaHistogramAPI.as_view(), name="karma-histogram"), | ||
| ] |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,65 @@ | ||||||||||||||||||||
| from django.db.models import Count, ExpressionWrapper, F, IntegerField, OuterRef, Subquery, Value | ||||||||||||||||||||
| from django.db.models.functions import Coalesce | ||||||||||||||||||||
| from rest_framework.views import APIView | ||||||||||||||||||||
|
|
||||||||||||||||||||
| from db.task import UserIgLink | ||||||||||||||||||||
| from db.learning_circle import UserCircleLink | ||||||||||||||||||||
| from db.user import User | ||||||||||||||||||||
| from utils.permission import CustomizePermission, JWTUtils, role_required | ||||||||||||||||||||
| from utils.response import CustomResponse | ||||||||||||||||||||
| from utils.types import RoleType | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| class StudentParticipationBreakdownAPI(APIView): | ||||||||||||||||||||
| authentication_classes = [CustomizePermission] | ||||||||||||||||||||
|
|
||||||||||||||||||||
| @role_required([RoleType.ADMIN.value, RoleType.FELLOW.value, RoleType.ASSOCIATE.value]) | ||||||||||||||||||||
| def get(self, request): | ||||||||||||||||||||
| ig_count_queryset = ( | ||||||||||||||||||||
| UserIgLink.objects.filter(user_id=OuterRef("pk")) | ||||||||||||||||||||
| .values("user_id") | ||||||||||||||||||||
| .annotate(total_igs=Count("ig", distinct=True)) | ||||||||||||||||||||
| .values("total_igs") | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| circle_count_queryset = ( | ||||||||||||||||||||
| UserCircleLink.objects.filter(user_id=OuterRef("pk"), accepted=True) | ||||||||||||||||||||
| .values("user_id") | ||||||||||||||||||||
| .annotate(total_circles=Count("circle", distinct=True)) | ||||||||||||||||||||
| .values("total_circles") | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| students = ( | ||||||||||||||||||||
| User.objects.filter(user_role_link_user__role__title=RoleType.STUDENT.value) | ||||||||||||||||||||
| .distinct() | ||||||||||||||||||||
| .select_related("wallet_user") | ||||||||||||||||||||
| .annotate( | ||||||||||||||||||||
| user_id=F("id"), | ||||||||||||||||||||
| karma=Coalesce("wallet_user__karma", Value(0)), | ||||||||||||||||||||
| ig_count=Coalesce( | ||||||||||||||||||||
| Subquery(ig_count_queryset, output_field=IntegerField()), Value(0) | ||||||||||||||||||||
| ), | ||||||||||||||||||||
| circle_count=Coalesce( | ||||||||||||||||||||
| Subquery(circle_count_queryset, output_field=IntegerField()), Value(0) | ||||||||||||||||||||
| ), | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| .annotate( | ||||||||||||||||||||
| total_participation=ExpressionWrapper( | ||||||||||||||||||||
| F("ig_count") + F("circle_count"), output_field=IntegerField() | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| .values( | ||||||||||||||||||||
| "user_id", | ||||||||||||||||||||
| "full_name", | ||||||||||||||||||||
| "karma", | ||||||||||||||||||||
| "ig_count", | ||||||||||||||||||||
| "circle_count", | ||||||||||||||||||||
| "total_participation", | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| .order_by("-karma", "full_name") | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| return CustomResponse( | ||||||||||||||||||||
| response={"students": list(students)}, | ||||||||||||||||||||
| general_message="Student participation breakdown fetched successfully", | ||||||||||||||||||||
| ).get_success_response() | ||||||||||||||||||||
|
Comment on lines
+62
to
+65
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| from django.urls import path | ||
|
|
||
| from . import student_views | ||
|
|
||
| urlpatterns = [ | ||
| path( | ||
| "participation-breakdown/", | ||
| student_views.StudentParticipationBreakdownAPI.as_view(), | ||
| name="participation-breakdown", | ||
| ), | ||
| ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
KarmaActivityLogmodel hasappraiser_approvedandpeer_approvedboolean 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 inflatetotal_karma_awardedwith unearned karma. The summary should restrict to approved entries.