From 90bca8ee76dc2420f6a58a134e7d6ecf24a859a6 Mon Sep 17 00:00:00 2001 From: Henrry Pulgarin <39854568+Henrrypg@users.noreply.github.com> Date: Thu, 20 Oct 2022 07:43:20 -0500 Subject: [PATCH 1/9] feat: add cli command to generate report (#277) Co-authored-by: Alejandro Cardenas --- openedx/features/survey_report/application.py | 6 +++++ .../survey_report/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/generate_report.py | 26 +++++++++++++++++++ .../management/commands/tests/__init__.py | 0 .../commands/tests/test_generate_report.py | 13 ++++++++++ 6 files changed, 45 insertions(+) create mode 100644 openedx/features/survey_report/application.py create mode 100644 openedx/features/survey_report/management/__init__.py create mode 100644 openedx/features/survey_report/management/commands/__init__.py create mode 100644 openedx/features/survey_report/management/commands/generate_report.py create mode 100644 openedx/features/survey_report/management/commands/tests/__init__.py create mode 100644 openedx/features/survey_report/management/commands/tests/test_generate_report.py diff --git a/openedx/features/survey_report/application.py b/openedx/features/survey_report/application.py new file mode 100644 index 000000000000..9ebba689351e --- /dev/null +++ b/openedx/features/survey_report/application.py @@ -0,0 +1,6 @@ +""" +Contains the logic for manage a survey report. +""" + +def generate_report() -> None: + """ Generate a report with relevant data.""" diff --git a/openedx/features/survey_report/management/__init__.py b/openedx/features/survey_report/management/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openedx/features/survey_report/management/commands/__init__.py b/openedx/features/survey_report/management/commands/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openedx/features/survey_report/management/commands/generate_report.py b/openedx/features/survey_report/management/commands/generate_report.py new file mode 100644 index 000000000000..29e9bb89bb93 --- /dev/null +++ b/openedx/features/survey_report/management/commands/generate_report.py @@ -0,0 +1,26 @@ +""" +CLI command to generate survey report. +""" + +from django.core.management.base import BaseCommand, CommandError +from openedx.features.survey_report.application import generate_report + +class Command(BaseCommand): + """ + Management command to generate a new survey report with + non-sensitive data. + """ + + help = """ + This command will create a new survey report using some + models to get the total number oof courses offered, currently active learners, + learners ever registered, and generated certificates. + """ + + def handle(self, *args, **options): + try: + generate_report() + except Exception as error: + raise CommandError('An error has ocurred while report was generating.') from error + + self.stdout.write(self.style.SUCCESS('Survey report has been generated successfully.')) diff --git a/openedx/features/survey_report/management/commands/tests/__init__.py b/openedx/features/survey_report/management/commands/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/openedx/features/survey_report/management/commands/tests/test_generate_report.py b/openedx/features/survey_report/management/commands/tests/test_generate_report.py new file mode 100644 index 000000000000..ab2982301e59 --- /dev/null +++ b/openedx/features/survey_report/management/commands/tests/test_generate_report.py @@ -0,0 +1,13 @@ +""" +Test for generate_report command. +""" + +from io import StringIO +from django.core.management import call_command +from django.test import TestCase + +class GenerateReportTest(TestCase): + def test_command_output(self): + out = StringIO() + call_command('generate_report', stdout=out) + self.assertIn('Survey report has been generated successfully.', out.getvalue()) From 6259ec22d30ba4ffa3d0dce0df1f6881b2b0fd61 Mon Sep 17 00:00:00 2001 From: henrrypg Date: Wed, 19 Oct 2022 14:43:32 -0500 Subject: [PATCH 2/9] feat: add method to get unique courses offered --- openedx/features/survey_report/application.py | 5 +++- openedx/features/survey_report/queries.py | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 openedx/features/survey_report/queries.py diff --git a/openedx/features/survey_report/application.py b/openedx/features/survey_report/application.py index 9ebba689351e..f22ac11bb12c 100644 --- a/openedx/features/survey_report/application.py +++ b/openedx/features/survey_report/application.py @@ -1,6 +1,9 @@ """ -Contains the logic for manage a survey report. +Contains the logic to manage survey report model. """ +from openedx.features.survey_report.queries import get_unique_courses_offered + def generate_report() -> None: """ Generate a report with relevant data.""" + courses_offered=get_unique_courses_offered() diff --git a/openedx/features/survey_report/queries.py b/openedx/features/survey_report/queries.py new file mode 100644 index 000000000000..497687b47657 --- /dev/null +++ b/openedx/features/survey_report/queries.py @@ -0,0 +1,27 @@ +""" +Queries to get data from database. +""" + +from datetime import datetime +from openedx.core.djangoapps.content.course_overviews.models import CourseOverview +from common.djangoapps.student.models import CourseEnrollment + + +from django.db.models import Q, Subquery, OuterRef, Count + +def get_unique_courses_offered() -> int: + """ + Get total number of unique courses offered. + """ + return CourseOverview.objects.annotate( + count = Subquery( + CourseEnrollment.objects + .filter(course_id=OuterRef('id')) + .values('course_id') + .annotate(count=Count('course_id')) + .values('count') + ))\ + .filter(count__gt=5)\ + .filter(start__lt=datetime.now())\ + .filter(Q(end__isnull=True)|Q(end__gt=datetime.now()))\ + .count() From eab43c18b791d4a3b36bc540b516b0165637c807 Mon Sep 17 00:00:00 2001 From: Henrry Pulgarin <39854568+Henrrypg@users.noreply.github.com> Date: Thu, 20 Oct 2022 21:18:35 -0500 Subject: [PATCH 3/9] feat: add method to get currently learners (#283) --- openedx/features/survey_report/application.py | 3 ++- openedx/features/survey_report/queries.py | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/openedx/features/survey_report/application.py b/openedx/features/survey_report/application.py index f22ac11bb12c..fce8f35a3c9c 100644 --- a/openedx/features/survey_report/application.py +++ b/openedx/features/survey_report/application.py @@ -2,8 +2,9 @@ Contains the logic to manage survey report model. """ -from openedx.features.survey_report.queries import get_unique_courses_offered +from openedx.features.survey_report.queries import get_unique_courses_offered, currently_learners def generate_report() -> None: """ Generate a report with relevant data.""" courses_offered=get_unique_courses_offered() + learners=currently_learners() diff --git a/openedx/features/survey_report/queries.py b/openedx/features/survey_report/queries.py index 497687b47657..03b34646093c 100644 --- a/openedx/features/survey_report/queries.py +++ b/openedx/features/survey_report/queries.py @@ -2,10 +2,10 @@ Queries to get data from database. """ -from datetime import datetime +from datetime import datetime, timedelta from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from common.djangoapps.student.models import CourseEnrollment - +from django.contrib.auth.models import User from django.db.models import Q, Subquery, OuterRef, Count @@ -25,3 +25,9 @@ def get_unique_courses_offered() -> int: .filter(start__lt=datetime.now())\ .filter(Q(end__isnull=True)|Q(end__gt=datetime.now()))\ .count() + +def currently_learners() -> int: + """ + Get total number of learners with last login in the last 3 weeks. + """ + return User.objects.filter(last_login__gte=datetime.now() - timedelta(weeks=3)).count() From a17664d66158c771098d0d3af353eb39410650b3 Mon Sep 17 00:00:00 2001 From: Henrry Pulgarin <39854568+Henrrypg@users.noreply.github.com> Date: Thu, 20 Oct 2022 22:03:40 -0500 Subject: [PATCH 4/9] feat: add method to get learners even registered (#284) --- openedx/features/survey_report/application.py | 3 ++- openedx/features/survey_report/queries.py | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/openedx/features/survey_report/application.py b/openedx/features/survey_report/application.py index fce8f35a3c9c..31ddf52e1486 100644 --- a/openedx/features/survey_report/application.py +++ b/openedx/features/survey_report/application.py @@ -2,9 +2,10 @@ Contains the logic to manage survey report model. """ -from openedx.features.survey_report.queries import get_unique_courses_offered, currently_learners +from openedx.features.survey_report.queries import get_unique_courses_offered, currently_learners, learners_registered def generate_report() -> None: """ Generate a report with relevant data.""" courses_offered=get_unique_courses_offered() learners=currently_learners() + registered=learners_registered() diff --git a/openedx/features/survey_report/queries.py b/openedx/features/survey_report/queries.py index 03b34646093c..43ae50619e15 100644 --- a/openedx/features/survey_report/queries.py +++ b/openedx/features/survey_report/queries.py @@ -31,3 +31,9 @@ def currently_learners() -> int: Get total number of learners with last login in the last 3 weeks. """ return User.objects.filter(last_login__gte=datetime.now() - timedelta(weeks=3)).count() + +def learners_registered()-> int: + """ + Get count of learners registered. + """ + return User.objects.filter(is_active=1).count() From 855a64c843cb7f3347be0521d27a9f5c10edec80 Mon Sep 17 00:00:00 2001 From: Henrry Pulgarin <39854568+Henrrypg@users.noreply.github.com> Date: Fri, 21 Oct 2022 12:15:11 -0500 Subject: [PATCH 5/9] feat: add method generated certificates (DS-272) (#285) --- openedx/features/survey_report/application.py | 15 +++++--- .../management/commands/generate_report.py | 6 ++-- .../commands/tests/test_generate_report.py | 2 ++ openedx/features/survey_report/models.py | 3 +- openedx/features/survey_report/queries.py | 34 +++++++++++++------ 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/openedx/features/survey_report/application.py b/openedx/features/survey_report/application.py index 31ddf52e1486..6c9bd19b016e 100644 --- a/openedx/features/survey_report/application.py +++ b/openedx/features/survey_report/application.py @@ -2,10 +2,17 @@ Contains the logic to manage survey report model. """ -from openedx.features.survey_report.queries import get_unique_courses_offered, currently_learners, learners_registered +from openedx.features.survey_report.queries import ( + currently_learners, + genarated_certificates, + get_unique_courses_offered, + learners_registered +) + def generate_report() -> None: """ Generate a report with relevant data.""" - courses_offered=get_unique_courses_offered() - learners=currently_learners() - registered=learners_registered() + courses_offered = get_unique_courses_offered() + learners = currently_learners() + registered = learners_registered() + certificates = genarated_certificates() diff --git a/openedx/features/survey_report/management/commands/generate_report.py b/openedx/features/survey_report/management/commands/generate_report.py index 29e9bb89bb93..3c6c031eac13 100644 --- a/openedx/features/survey_report/management/commands/generate_report.py +++ b/openedx/features/survey_report/management/commands/generate_report.py @@ -3,8 +3,10 @@ """ from django.core.management.base import BaseCommand, CommandError + from openedx.features.survey_report.application import generate_report + class Command(BaseCommand): """ Management command to generate a new survey report with @@ -17,10 +19,10 @@ class Command(BaseCommand): learners ever registered, and generated certificates. """ - def handle(self, *args, **options): + def handle(self, *_args, **_options): try: generate_report() except Exception as error: - raise CommandError('An error has ocurred while report was generating.') from error + raise CommandError('An error has occurred while report was generating.') from error self.stdout.write(self.style.SUCCESS('Survey report has been generated successfully.')) diff --git a/openedx/features/survey_report/management/commands/tests/test_generate_report.py b/openedx/features/survey_report/management/commands/tests/test_generate_report.py index ab2982301e59..0bcfa9301424 100644 --- a/openedx/features/survey_report/management/commands/tests/test_generate_report.py +++ b/openedx/features/survey_report/management/commands/tests/test_generate_report.py @@ -3,9 +3,11 @@ """ from io import StringIO + from django.core.management import call_command from django.test import TestCase + class GenerateReportTest(TestCase): def test_command_output(self): out = StringIO() diff --git a/openedx/features/survey_report/models.py b/openedx/features/survey_report/models.py index a9b3c2dd017b..d95411438650 100644 --- a/openedx/features/survey_report/models.py +++ b/openedx/features/survey_report/models.py @@ -2,9 +2,8 @@ Survey Report models. """ -from jsonfield import JSONField - from django.db import models +from jsonfield import JSONField class SurveyReport(models.Model): diff --git a/openedx/features/survey_report/queries.py b/openedx/features/survey_report/queries.py index 43ae50619e15..c1df820bdf98 100644 --- a/openedx/features/survey_report/queries.py +++ b/openedx/features/survey_report/queries.py @@ -3,37 +3,49 @@ """ from datetime import datetime, timedelta -from openedx.core.djangoapps.content.course_overviews.models import CourseOverview + +from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user +from django.db.models import Count, OuterRef, Q, Subquery + from common.djangoapps.student.models import CourseEnrollment -from django.contrib.auth.models import User +from lms.djangoapps.grades.models import PersistentCourseGrade +from openedx.core.djangoapps.content.course_overviews.models import CourseOverview -from django.db.models import Q, Subquery, OuterRef, Count def get_unique_courses_offered() -> int: """ Get total number of unique courses offered. """ return CourseOverview.objects.annotate( - count = Subquery( + count=Subquery( CourseEnrollment.objects - .filter(course_id=OuterRef('id')) - .values('course_id') - .annotate(count=Count('course_id')) - .values('count') - ))\ + .filter(course_id=OuterRef('id')) + .values('course_id') + .annotate(count=Count('course_id')) + .values('count') + ))\ .filter(count__gt=5)\ .filter(start__lt=datetime.now())\ - .filter(Q(end__isnull=True)|Q(end__gt=datetime.now()))\ + .filter(Q(end__isnull=True) | Q(end__gt=datetime.now()))\ .count() + def currently_learners() -> int: """ Get total number of learners with last login in the last 3 weeks. """ return User.objects.filter(last_login__gte=datetime.now() - timedelta(weeks=3)).count() -def learners_registered()-> int: + +def learners_registered() -> int: """ Get count of learners registered. """ return User.objects.filter(is_active=1).count() + + +def generated_certificates() -> int: + """ + Get count of generated certificates. + """ + return PersistentCourseGrade.objects.filter(passed_timestamp__isnull=False).count() From 86ab38f3c8e51d656d8eb151310e431c4f074b92 Mon Sep 17 00:00:00 2001 From: Alejandro Cardenas Date: Fri, 21 Oct 2022 16:01:10 -0500 Subject: [PATCH 6/9] feat: add method to get course enrollments --- openedx/features/survey_report/application.py | 4 +++- openedx/features/survey_report/queries.py | 19 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/openedx/features/survey_report/application.py b/openedx/features/survey_report/application.py index 6c9bd19b016e..031fa68a057e 100644 --- a/openedx/features/survey_report/application.py +++ b/openedx/features/survey_report/application.py @@ -6,7 +6,8 @@ currently_learners, genarated_certificates, get_unique_courses_offered, - learners_registered + learners_registered, + course_enrollments ) @@ -16,3 +17,4 @@ def generate_report() -> None: learners = currently_learners() registered = learners_registered() certificates = genarated_certificates() + enrollments = course_enrollments() diff --git a/openedx/features/survey_report/queries.py b/openedx/features/survey_report/queries.py index c1df820bdf98..b02f9a79bdbf 100644 --- a/openedx/features/survey_report/queries.py +++ b/openedx/features/survey_report/queries.py @@ -11,7 +11,6 @@ from lms.djangoapps.grades.models import PersistentCourseGrade from openedx.core.djangoapps.content.course_overviews.models import CourseOverview - def get_unique_courses_offered() -> int: """ Get total number of unique courses offered. @@ -30,22 +29,28 @@ def get_unique_courses_offered() -> int: .count() -def currently_learners() -> int: +def get_currently_learners() -> int: """ Get total number of learners with last login in the last 3 weeks. """ return User.objects.filter(last_login__gte=datetime.now() - timedelta(weeks=3)).count() -def learners_registered() -> int: +def get_learners_registered() -> int: """ - Get count of learners registered. + Get total number of active learners registered. """ - return User.objects.filter(is_active=1).count() + return User.objects.filter(is_active=True).count() -def generated_certificates() -> int: +def get_generated_certificates() -> int: """ - Get count of generated certificates. + Get total number of generated certificates. """ return PersistentCourseGrade.objects.filter(passed_timestamp__isnull=False).count() + +def get_course_enrollments() -> int: + """" + Get total number of enrollments from users that aren't staff. + """ + return CourseEnrollment.objects.filter(user__is_superuser=False, user__is_staff=False).count() From 910901221996514d24e842080c82cb052246bf90 Mon Sep 17 00:00:00 2001 From: Alejandro Cardenas Date: Mon, 24 Oct 2022 06:58:58 -0500 Subject: [PATCH 7/9] feat: save survey report --- openedx/features/survey_report/application.py | 26 +++++++++++++------ openedx/features/survey_report/queries.py | 2 +- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/openedx/features/survey_report/application.py b/openedx/features/survey_report/application.py index 031fa68a057e..c54919206d1f 100644 --- a/openedx/features/survey_report/application.py +++ b/openedx/features/survey_report/application.py @@ -3,18 +3,28 @@ """ from openedx.features.survey_report.queries import ( - currently_learners, - genarated_certificates, get_unique_courses_offered, - learners_registered, - course_enrollments + get_currently_learners, + get_learners_registered, + get_generated_certificates, + get_course_enrollments, ) +from openedx.features.survey_report.models import SurveyReport + def generate_report() -> None: """ Generate a report with relevant data.""" courses_offered = get_unique_courses_offered() - learners = currently_learners() - registered = learners_registered() - certificates = genarated_certificates() - enrollments = course_enrollments() + learners = get_currently_learners() + registered = get_learners_registered() + certificates = get_generated_certificates() + enrollments = get_course_enrollments() + + SurveyReport( + courses_offered=courses_offered, + learners=learners, + learners_registered=registered, + generated_certificates=certificates, + enrollments=enrollments + ) diff --git a/openedx/features/survey_report/queries.py b/openedx/features/survey_report/queries.py index b02f9a79bdbf..589dfad127f3 100644 --- a/openedx/features/survey_report/queries.py +++ b/openedx/features/survey_report/queries.py @@ -50,7 +50,7 @@ def get_generated_certificates() -> int: return PersistentCourseGrade.objects.filter(passed_timestamp__isnull=False).count() def get_course_enrollments() -> int: - """" + """ Get total number of enrollments from users that aren't staff. """ return CourseEnrollment.objects.filter(user__is_superuser=False, user__is_staff=False).count() From 9f65641958e98d37f1491f53260911056a5679cc Mon Sep 17 00:00:00 2001 From: Alejandro Cardenas Date: Mon, 24 Oct 2022 09:22:09 -0500 Subject: [PATCH 8/9] feat: add extra_data setting Co-authored-by: henrrypg --- lms/envs/production.py | 3 +++ openedx/features/survey_report/application.py | 12 +++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lms/envs/production.py b/lms/envs/production.py index d1c5a08aa2b0..8455f76948a0 100644 --- a/lms/envs/production.py +++ b/lms/envs/production.py @@ -1080,3 +1080,6 @@ def get_env_setting(setting): "SECRET": ENV_TOKENS.get('BIG_BLUE_BUTTON_GLOBAL_SECRET', None), "URL": ENV_TOKENS.get('BIG_BLUE_BUTTON_GLOBAL_URL', None), } + +############## Settings for survey report ############## +SURVEY_REPORT_EXTRA_DATA = ENV_TOKENS.get('SURVEY_REPORT_EXTRA_DATA', {}) diff --git a/openedx/features/survey_report/application.py b/openedx/features/survey_report/application.py index c54919206d1f..89ee9a2ef2b6 100644 --- a/openedx/features/survey_report/application.py +++ b/openedx/features/survey_report/application.py @@ -2,6 +2,8 @@ Contains the logic to manage survey report model. """ +from django.conf import settings + from openedx.features.survey_report.queries import ( get_unique_courses_offered, get_currently_learners, @@ -20,11 +22,15 @@ def generate_report() -> None: registered = get_learners_registered() certificates = get_generated_certificates() enrollments = get_course_enrollments() + extra_data = settings.SURVEY_REPORT_EXTRA_DATA - SurveyReport( + survey_report = SurveyReport( courses_offered=courses_offered, learners=learners, - learners_registered=registered, + registered_learners=registered, generated_certificates=certificates, - enrollments=enrollments + enrollments=enrollments, + extra_data=extra_data, ) + + survey_report.save() From 54a8d671274a344a2336926b7965e98123e573ab Mon Sep 17 00:00:00 2001 From: Henrry Pulgarin <39854568+Henrrypg@users.noreply.github.com> Date: Wed, 26 Oct 2022 08:41:11 -0500 Subject: [PATCH 9/9] feat: add tests to queries and survey_report command (#290) --- lms/envs/test.py | 3 + openedx/features/survey_report/application.py | 9 +- .../management/commands/generate_report.py | 2 +- .../commands/tests/test_generate_report.py | 35 +++++- openedx/features/survey_report/queries.py | 2 + .../survey_report/tests/test_query_methods.py | 114 ++++++++++++++++++ 6 files changed, 156 insertions(+), 9 deletions(-) create mode 100644 openedx/features/survey_report/tests/test_query_methods.py diff --git a/lms/envs/test.py b/lms/envs/test.py index ad9372e09be1..717ee1d4c458 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -669,3 +669,6 @@ "LOGO_URL": "https://courses.example.com/yourmfe-logo.png", }, } + +############## Settings for survey report ############## +SURVEY_REPORT_EXTRA_DATA = {} diff --git a/openedx/features/survey_report/application.py b/openedx/features/survey_report/application.py index 89ee9a2ef2b6..776433d8ce98 100644 --- a/openedx/features/survey_report/application.py +++ b/openedx/features/survey_report/application.py @@ -4,16 +4,15 @@ from django.conf import settings +from openedx.features.survey_report.models import SurveyReport from openedx.features.survey_report.queries import ( - get_unique_courses_offered, + get_course_enrollments, get_currently_learners, - get_learners_registered, get_generated_certificates, - get_course_enrollments, + get_learners_registered, + get_unique_courses_offered ) -from openedx.features.survey_report.models import SurveyReport - def generate_report() -> None: """ Generate a report with relevant data.""" diff --git a/openedx/features/survey_report/management/commands/generate_report.py b/openedx/features/survey_report/management/commands/generate_report.py index 3c6c031eac13..475469dbfe37 100644 --- a/openedx/features/survey_report/management/commands/generate_report.py +++ b/openedx/features/survey_report/management/commands/generate_report.py @@ -23,6 +23,6 @@ def handle(self, *_args, **_options): try: generate_report() except Exception as error: - raise CommandError('An error has occurred while report was generating.') from error + raise CommandError(f'An error has occurred while report was generating. {error}') from error self.stdout.write(self.style.SUCCESS('Survey report has been generated successfully.')) diff --git a/openedx/features/survey_report/management/commands/tests/test_generate_report.py b/openedx/features/survey_report/management/commands/tests/test_generate_report.py index 0bcfa9301424..45f0d553c86d 100644 --- a/openedx/features/survey_report/management/commands/tests/test_generate_report.py +++ b/openedx/features/survey_report/management/commands/tests/test_generate_report.py @@ -3,13 +3,42 @@ """ from io import StringIO +from unittest import mock from django.core.management import call_command -from django.test import TestCase +from django.test import TestCase, override_settings + +from openedx.features.survey_report.models import SurveyReport class GenerateReportTest(TestCase): - def test_command_output(self): + """ + Test for generate_report command. + """ + @override_settings(SURVEY_REPORT_EXTRA_DATA={'extra_data': 'extra_data'}) + @mock.patch('openedx.features.survey_report.queries.get_course_enrollments') + @mock.patch('openedx.features.survey_report.queries.get_generated_certificates') + @mock.patch('openedx.features.survey_report.queries.get_learners_registered') + @mock.patch('openedx.features.survey_report.queries.get_currently_learners') + @mock.patch('openedx.features.survey_report.queries.get_unique_courses_offered') + def test_generate_report(self, mock_get_unique_courses_offered, mock_get_currently_learners, + mock_get_learners_registered, mock_get_generated_certificates, + mock_get_course_enrollments): + """ + Test that generate_report command creates a survey report. + """ + mock_get_unique_courses_offered.return_value = 1 + mock_get_currently_learners.return_value = 2 + mock_get_learners_registered.return_value = 3 + mock_get_generated_certificates.return_value = 4 + mock_get_course_enrollments.return_value = 5 out = StringIO() call_command('generate_report', stdout=out) - self.assertIn('Survey report has been generated successfully.', out.getvalue()) + + survey_report = SurveyReport.objects.last() + assert survey_report.courses_offered == 1 + assert survey_report.learners == 2 + assert survey_report.registered_learners == 3 + assert survey_report.generated_certificates == 4 + assert survey_report.enrollments == 5 + assert survey_report.extra_data == {'extra_data': 'extra_data'} diff --git a/openedx/features/survey_report/queries.py b/openedx/features/survey_report/queries.py index 589dfad127f3..d2e179482cb8 100644 --- a/openedx/features/survey_report/queries.py +++ b/openedx/features/survey_report/queries.py @@ -11,6 +11,7 @@ from lms.djangoapps.grades.models import PersistentCourseGrade from openedx.core.djangoapps.content.course_overviews.models import CourseOverview + def get_unique_courses_offered() -> int: """ Get total number of unique courses offered. @@ -49,6 +50,7 @@ def get_generated_certificates() -> int: """ return PersistentCourseGrade.objects.filter(passed_timestamp__isnull=False).count() + def get_course_enrollments() -> int: """ Get total number of enrollments from users that aren't staff. diff --git a/openedx/features/survey_report/tests/test_query_methods.py b/openedx/features/survey_report/tests/test_query_methods.py new file mode 100644 index 000000000000..e426a38886f9 --- /dev/null +++ b/openedx/features/survey_report/tests/test_query_methods.py @@ -0,0 +1,114 @@ +""" +Test for survey report commands. +""" + +from datetime import datetime, timedelta +from unittest.mock import patch + +from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory +from lms.djangoapps.grades.models import PersistentCourseGrade +from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory +from openedx.features.survey_report.queries import ( + get_course_enrollments, + get_currently_learners, + get_generated_certificates, + get_learners_registered, + get_unique_courses_offered +) +from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order +from xmodule.modulestore.django import modulestore +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order + + +class TestSurveyReportCommands(ModuleStoreTestCase): + """ + Test for survey report query methods. + """ + + def setUp(self): + """ + Setup for users and courses. + """ + super().setUp() + self.store = modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.mongo) # lint-amnesty, pylint: disable=protected-access + self.first_course = CourseFactory.create( + org="test", course="course1", display_name="run1", default_store=ModuleStoreEnum.Type.mongo + ) + self.user = UserFactory.create(username='test_user', email='test@example.com', password='password') + self.user1 = UserFactory.create(username='test_user1', email='test1@example.com', password='password') + self.user2 = UserFactory.create(username='test_user2', email='test2@example.com', password='password') + self.user3 = UserFactory.create(username='test_user3', email='test3@example.com', password='password') + self.user4 = UserFactory.create(username='test_user4', email='test4@example.com', password='password') + self.user5 = UserFactory.create(username='test_user5', email='test5@example.com', password='password') + + def test_get_unique_courses_offered(self): + """ + Test that get_unique_courses_offered returns the correct number of courses. + """ + course_overview = CourseOverviewFactory.create(id=self.first_course.id, start="2019-01-01", end="2024-01-01") + CourseEnrollmentFactory.create(user=self.user, course_id=course_overview.id) + CourseEnrollmentFactory.create(user=self.user1, course_id=course_overview.id) + CourseEnrollmentFactory.create(user=self.user2, course_id=course_overview.id) + CourseEnrollmentFactory.create(user=self.user3, course_id=course_overview.id) + CourseEnrollmentFactory.create(user=self.user4, course_id=course_overview.id) + CourseEnrollmentFactory.create(user=self.user5, course_id=course_overview.id) + with patch('openedx.features.survey_report.queries.datetime') as mock_datetime: + mock_datetime.now.return_value = datetime.now() + assert get_unique_courses_offered() == 1 + + def test_get_currently_learners(self): + """ + Test that get_currently_learners returns the correct number of learners. + """ + self.user.last_login = datetime.now() - timedelta(days=1) + self.user.save() + self.user1.last_login = datetime.now() - timedelta(weeks=2) + self.user1.save() + self.user2.last_login = datetime.now() - timedelta(weeks=4) + self.user2.save() + with patch('openedx.features.survey_report.queries.datetime') as mock_datetime: + mock_datetime.now.return_value = datetime.now() + assert get_currently_learners() == 2 + + def test_get_learners_registered(self): + """ + Test that get_learners_registered returns the correct number of learners. + """ + assert get_learners_registered() == 7 + + def test_get_generated_certificates(self): + """ + Test that get_generated_certificates returns the correct number of certificates. + """ + course_grade_params = { + "user_id": self.user.id, + "course_id": self.first_course.id, + "percent_grade": 77.7, + "letter_grade": "pass", + "passed": True, + "passed_timestamp": datetime.now(), + } + PersistentCourseGrade.update_or_create(**course_grade_params) + + with patch('openedx.features.survey_report.queries.datetime') as mock_datetime: + mock_datetime.now.return_value = datetime.now() + assert get_generated_certificates() == 1 + + def test_get_course_enrollments(self): + """ + Test that get_course_enrollments returns the correct number of enrollments. + """ + self.user.is_superuser = True + self.user.save() + self.user1.is_staff = True + self.user1.save() + course_overview = CourseOverviewFactory.create(id=self.first_course.id, start="2019-01-01", end="2024-01-01") + CourseEnrollmentFactory.create(user=self.user, course_id=course_overview.id) + CourseEnrollmentFactory.create(user=self.user1, course_id=course_overview.id) + CourseEnrollmentFactory.create(user=self.user2, course_id=course_overview.id) + CourseEnrollmentFactory.create(user=self.user3, course_id=course_overview.id) + CourseEnrollmentFactory.create(user=self.user4, course_id=course_overview.id) + with patch('openedx.features.survey_report.queries.datetime') as mock_datetime: + mock_datetime.now.return_value = datetime.now() + assert get_course_enrollments() == 3