From 21b8516d9da661928e1fb32ad85b4a521bd041ab Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Tue, 9 Jul 2024 10:18:32 +0500 Subject: [PATCH] feat!: modify API return watch hours, courses count and programs count --- .../course_progress/api/v1/urls.py | 4 +- .../course_progress/api/v1/views.py | 73 +++++++++++++++++-- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/openedx/features/sdaia_features/course_progress/api/v1/urls.py b/openedx/features/sdaia_features/course_progress/api/v1/urls.py index 38ddc36116d2..53cda962ae5f 100644 --- a/openedx/features/sdaia_features/course_progress/api/v1/urls.py +++ b/openedx/features/sdaia_features/course_progress/api/v1/urls.py @@ -3,11 +3,11 @@ """ from django.urls import path # pylint: disable=unused-import -from .views import UserWatchHoursAPIView +from .views import UserStatsAPIView app_name = "nafath_api_v1" urlpatterns = [ - path(r"user_watch_hours", UserWatchHoursAPIView.as_view()), + path(r"user_stats", UserStatsAPIView.as_view()), ] diff --git a/openedx/features/sdaia_features/course_progress/api/v1/views.py b/openedx/features/sdaia_features/course_progress/api/v1/views.py index be8baa7f4524..95273e8e13bd 100644 --- a/openedx/features/sdaia_features/course_progress/api/v1/views.py +++ b/openedx/features/sdaia_features/course_progress/api/v1/views.py @@ -5,6 +5,7 @@ import logging import requests +from ccx_keys.locator import CCXLocator from django.conf import settings from django.utils.decorators import method_decorator from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication @@ -12,8 +13,19 @@ from rest_framework.response import Response from rest_framework.views import APIView +from common.djangoapps.student.roles import ( + CourseInstructorRole, + CourseStaffRole, + UserBasedRole, +) from common.djangoapps.util.disable_rate_limit import can_disable_rate_limit +from lms.djangoapps.program_enrollments.rest_api.v1.views import ( + UserProgramReadOnlyAccessView, +) +from openedx.core.djangoapps.catalog.utils import get_programs, get_programs_by_type from openedx.core.djangoapps.cors_csrf.decorators import ensure_csrf_cookie_cross_domain +from openedx.core.djangoapps.enrollments.errors import CourseEnrollmentError +from openedx.core.djangoapps.enrollments.data import get_course_enrollments from openedx.core.djangoapps.enrollments.views import EnrollmentCrossDomainSessionAuth from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser from openedx.core.lib.api.permissions import ApiKeyHeaderPermissionIsAuthenticated @@ -23,16 +35,18 @@ @can_disable_rate_limit -class UserWatchHoursAPIView(APIView): +class UserStatsAPIView(APIView): """ APIView to get the total watch hours for a user. **Example Requests** - GET /sdaia/api/v1/user_watch_hours + GET /sdaia/api/v1/user_stats It return watch_time in hours Response: { - "watch_time": 0.00043390860160191856 + "watch_hours": 0.00043390860160191856, + "enrolled_courses": enrolled_courses, + "enrolled_programs": enrolled_programs, } """ @@ -48,19 +62,68 @@ def get(self, request): """ Gets the total watch hours for a user. """ - user_id = request.user.id + user = request.user + user_id = user.id clickhouse_uri = ( f"{settings.CAIRN_CLICKHOUSE_HTTP_SCHEME}://{settings.CAIRN_CLICKHOUSE_USERNAME}:{settings.CAIRN_CLICKHOUSE_PASSWORD}@" f"{settings.CAIRN_CLICKHOUSE_HOST}:{settings.CAIRN_CLICKHOUSE_HTTP_PORT}/?database={settings.CAIRN_CLICKHOUSE_DATABASE}" ) query = f"SELECT SUM(duration) as `Watch time` FROM `openedx`.`video_view_segments` WHERE user_id={user_id};" + ############ WATCH HOURS ############ try: response = requests.get(clickhouse_uri, data=query.encode("utf8")) watch_time = float(response.content.decode().strip()) / (60 * 60) - return Response(status=status.HTTP_200_OK, data={"watch_time": watch_time}) except Exception as e: log.error( f"Unable to fetch watch for user {user_id} due to this exception: {str(e)}" ) raise HTTPException(status_code=500, detail=str(e)) + + ############ PROGRAMS COUNT ############ + programs = [] + requested_program_type = "masters" + if user.is_staff: + programs = get_programs_by_type(request.site, requested_program_type) + else: + program_dict = {} + # Check if the user is a course staff of any course which is a part of a program. + programs_user_is_staff_for = ( + UserProgramReadOnlyAccessView().get_programs_user_is_course_staff_for( + user, requested_program_type + ) + ) + for staff_program in programs_user_is_staff_for: + program_dict.setdefault(staff_program["uuid"], staff_program) + + # Now get the program enrollments for user purely as a learner add to the list + enrolled_programs = ( + UserProgramReadOnlyAccessView()._get_enrolled_programs_from_model(user) + ) + for learner_program in enrolled_programs: + program_dict.setdefault(learner_program["uuid"], learner_program) + + programs = list(program_dict.values()) + enrolled_programs = len(programs) + + ############ COURSES COUNT ############ + username = user.username + try: + enrolled_courses = len(get_course_enrollments(username)) + except CourseEnrollmentError: + return Response( + status=status.HTTP_400_BAD_REQUEST, + data={ + "message": f"An error occurred while retrieving enrollments for user {username}" + }, + ) + + ############ Response ############ + return Response( + status=status.HTTP_200_OK, + data={ + "watch_hours": watch_time, + "enrolled_courses": enrolled_courses, + "enrolled_programs": enrolled_programs, + }, + )