From 8b3ae9f1e5eaf476eaae3a85045b690a493ed4ad Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Fri, 5 Jul 2024 16:15:21 +0500 Subject: [PATCH] feat: add an API for total watch hours of the user (#559) * feat: add an API for total watch hours of the user * address comments --------- Co-authored-by: Muhammad Faraz Maqsood --- .../course_progress/api/__init__.py | 0 .../course_progress/api/v1/__init__.py | 0 .../course_progress/api/v1/urls.py | 13 ++++ .../course_progress/api/v1/views.py | 66 +++++++++++++++++++ .../sdaia_features/course_progress/apps.py | 15 ++++- .../sdaia_features/course_progress/urls.py | 14 ++++ 6 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 openedx/features/sdaia_features/course_progress/api/__init__.py create mode 100644 openedx/features/sdaia_features/course_progress/api/v1/__init__.py create mode 100644 openedx/features/sdaia_features/course_progress/api/v1/urls.py create mode 100644 openedx/features/sdaia_features/course_progress/api/v1/views.py create mode 100644 openedx/features/sdaia_features/course_progress/urls.py diff --git a/openedx/features/sdaia_features/course_progress/api/__init__.py b/openedx/features/sdaia_features/course_progress/api/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openedx/features/sdaia_features/course_progress/api/v1/__init__.py b/openedx/features/sdaia_features/course_progress/api/v1/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openedx/features/sdaia_features/course_progress/api/v1/urls.py b/openedx/features/sdaia_features/course_progress/api/v1/urls.py new file mode 100644 index 000000000000..38ddc36116d2 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/api/v1/urls.py @@ -0,0 +1,13 @@ +""" +URLs for User Watch Hours - SDAIA Specific. +""" + +from django.urls import path # pylint: disable=unused-import +from .views import UserWatchHoursAPIView + + +app_name = "nafath_api_v1" + +urlpatterns = [ + path(r"user_watch_hours", UserWatchHoursAPIView.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 new file mode 100644 index 000000000000..be8baa7f4524 --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/api/v1/views.py @@ -0,0 +1,66 @@ +""" +The User Watch Hours API view - SDAIA Specific. +""" + +import logging +import requests + +from django.conf import settings +from django.utils.decorators import method_decorator +from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication +from rest_framework import permissions, status +from rest_framework.response import Response +from rest_framework.views import APIView + +from common.djangoapps.util.disable_rate_limit import can_disable_rate_limit +from openedx.core.djangoapps.cors_csrf.decorators import ensure_csrf_cookie_cross_domain +from openedx.core.djangoapps.enrollments.views import EnrollmentCrossDomainSessionAuth +from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser +from openedx.core.lib.api.permissions import ApiKeyHeaderPermissionIsAuthenticated + + +log = logging.getLogger(__name__) + + +@can_disable_rate_limit +class UserWatchHoursAPIView(APIView): + """ + APIView to get the total watch hours for a user. + + **Example Requests** + GET /sdaia/api/v1/user_watch_hours + + It return watch_time in hours + Response: { + "watch_time": 0.00043390860160191856 + } + """ + + authentication_classes = ( + JwtAuthentication, + BearerAuthenticationAllowInactiveUser, + EnrollmentCrossDomainSessionAuth, + ) + permission_classes = (ApiKeyHeaderPermissionIsAuthenticated,) + + @method_decorator(ensure_csrf_cookie_cross_domain) + def get(self, request): + """ + Gets the total watch hours for a user. + """ + user_id = request.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};" + + 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)) diff --git a/openedx/features/sdaia_features/course_progress/apps.py b/openedx/features/sdaia_features/course_progress/apps.py index fee8bda6b496..7f9a6ed4ee13 100644 --- a/openedx/features/sdaia_features/course_progress/apps.py +++ b/openedx/features/sdaia_features/course_progress/apps.py @@ -1,19 +1,28 @@ """ Progress Updates App Config """ + from django.apps import AppConfig from edx_django_utils.plugins import PluginURLs, PluginSettings from openedx.core.djangoapps.plugins.constants import ProjectType, SettingsType + class CourseProgressConfig(AppConfig): - name = 'openedx.features.sdaia_features.course_progress' + name = "openedx.features.sdaia_features.course_progress" plugin_app = { + PluginURLs.CONFIG: { + ProjectType.LMS: { + PluginURLs.NAMESPACE: "course_progress", + PluginURLs.REGEX: r"^sdaia", + PluginURLs.RELATIVE_PATH: "urls", + } + }, PluginSettings.CONFIG: { ProjectType.LMS: { - SettingsType.COMMON: {PluginSettings.RELATIVE_PATH: 'settings.common'}, + SettingsType.COMMON: {PluginSettings.RELATIVE_PATH: "settings.common"}, } - } + }, } def ready(self): diff --git a/openedx/features/sdaia_features/course_progress/urls.py b/openedx/features/sdaia_features/course_progress/urls.py new file mode 100644 index 000000000000..5d9be29a3cdd --- /dev/null +++ b/openedx/features/sdaia_features/course_progress/urls.py @@ -0,0 +1,14 @@ +""" +URLs for User Watch Hours - SDAIA Specific. +""" + +from django.urls import path # pylint: disable=unused-import +from django.conf.urls import include + + +urlpatterns = [ + path( + "/api/v1/", + include("openedx.features.sdaia_features.course_progress.api.v1.urls", namespace="course_progress_api_v1"), + ), +]