diff --git a/lms/envs/common.py b/lms/envs/common.py index 13e031c76175..b83341086642 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1324,7 +1324,12 @@ def _make_mako_template_dirs(settings): 'openedx.core.djangoapps.site_configuration.context_processors.configuration_context', # Mobile App processor (Detects if request is from the mobile app) - 'lms.djangoapps.mobile_api.context_processor.is_from_mobile_app' + 'lms.djangoapps.mobile_api.context_processor.is_from_mobile_app', + + # Context processor necesarry for the survey report message appear on the admin site + 'openedx.features.survey_report.context_processors.admin_extra_context' + + ] # Django templating @@ -5389,3 +5394,31 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring #### Event bus publishing #### ## Will be more filled out as part of https://github.com/edx/edx-arch-experiments/issues/381 EVENT_BUS_PRODUCER_CONFIG = {} + +#### Survey Report #### +# .. toggle_name: SURVEY_REPORT_ENABLE +# .. toggle_implementation: DjangoSetting +# .. toggle_default: True +# .. toggle_description: Set to True to enable the feature to generate and send survey reports. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2024-01-30 +SURVEY_REPORT_ENABLE = True +# .. setting_name: SURVEY_REPORT_ENDPOINT +# .. setting_default: Open edX organization endpoint +# .. setting_description: Endpoint where the report will be sent. +SURVEY_REPORT_ENDPOINT = 'https://hooks.zapier.com/hooks/catch/11595998/3ouwv7m/' +# .. toggle_name: ANONYMOUS_SURVEY_REPORT +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: If enable, the survey report will be send a UUID as ID instead of use lms site name. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2023-02-21 +ANONYMOUS_SURVEY_REPORT = False +# .. setting_name: SURVEY_REPORT_CHECK_THRESHOLD +# .. setting_default: every 6 months +# .. setting_description: Survey report banner will appear if a survey report is not sent in the months defined. +SURVEY_REPORT_CHECK_THRESHOLD = 6 +# .. setting_name: SURVEY_REPORT_EXTRA_DATA +# .. setting_default: empty dictionary +# .. setting_description: Dictionary with additional information that you want to share in the report. +SURVEY_REPORT_EXTRA_DATA = {} diff --git a/lms/envs/production.py b/lms/envs/production.py index 0233fce6a59e..6f04ba7f92c8 100644 --- a/lms/envs/production.py +++ b/lms/envs/production.py @@ -1117,12 +1117,6 @@ def get_env_setting(setting): "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', {}) -SURVEY_REPORT_ENDPOINT = ENV_TOKENS.get('SURVEY_REPORT_ENDPOINT', - 'https://hooks.zapier.com/hooks/catch/11595998/3ouwv7m/') -ANONYMOUS_SURVEY_REPORT = False - AVAILABLE_DISCUSSION_TOURS = ENV_TOKENS.get('AVAILABLE_DISCUSSION_TOURS', []) ############## NOTIFICATIONS EXPIRY ############## diff --git a/lms/envs/test.py b/lms/envs/test.py index 32352c849837..c26725469dd1 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -667,6 +667,8 @@ ############## Settings for survey report ############## SURVEY_REPORT_EXTRA_DATA = {} SURVEY_REPORT_ENDPOINT = "https://example.com/survey_report" +SURVEY_REPORT_CHECK_THRESHOLD = 6 +SURVEY_REPORT_ENABLE = True ANONYMOUS_SURVEY_REPORT = False ######################## Subscriptions API SETTINGS ######################## diff --git a/lms/templates/admin/base_site.html b/lms/templates/admin/base_site.html index 4ea86307696e..cd8ab70e4f4b 100644 --- a/lms/templates/admin/base_site.html +++ b/lms/templates/admin/base_site.html @@ -1,5 +1,5 @@ {% extends "admin/base.html" %} -{% load i18n admin_urls %} +{% load i18n admin_urls static %} {% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %} {% block branding %}

{{ site_header|default:_('Django administration') }}

@@ -20,3 +20,11 @@

{{ site_header|default:_('D {% endblock %} + +{% block extrahead %} + +{% endblock %} + +{% block messages %}{{ block.super }} + {% include "survey_report/admin_banner.html" %} +{% endblock %} diff --git a/openedx/features/survey_report/README.rst b/openedx/features/survey_report/README.rst index 06c120ba9cb2..412e319f222d 100644 --- a/openedx/features/survey_report/README.rst +++ b/openedx/features/survey_report/README.rst @@ -1,11 +1,78 @@ Survey Report --------------------- -This Django app was created for the purpose of gathering aggregated, anonymized data -about Open edX courses at scale, so that we can begin to track the growth -and trends in Open edX usage over time, namely in the annual Open edX -Impact Report. - -You could find in this directory some methods to manage survey -reports, one command to generate the report, some queries to get the -information from database and one method to send the report to openedx -api. +=============== + +This Django app was created to gather aggregated, anonymized data about Open edX courses at scale so that we can begin to track the growth and trends in Open edX usage over time, namely in the annual Open edX Impact Report. + +With this app, you can collect the following information on your platform: + +- ``courses_offered``: Total number of active unique courses. +- ``learners``: Recently active users with login in the last four weeks. +- ``registered_learners``: Total number of users ever registered in the platform. +- ``enrollments``: Total number of active enrollments in the platform. +- ``generated_certificates``: Total number of generated certificates. +- ``extra_data``: Extra information that will be saved in the report, e.g., site_name, openedx-release. +- ``state``: State of the async generating process. + +You can find in this directory: +- Some methods to manage survey reports. +- One command to generate the report. +- Some queries to get the information from the database. +- One method to send the report to Open edX API. + +How to Generate a Report and Send It +------------------------------------- + +By setting ``SURVEY_REPORT_ENDPOINT``, you can choose to whom you would like to send the report; by default, you will send the report to the Open edX organization to collaborate with the annual Open edX Impact Report. You can see `Settings for Survey Report`_ for more information. + +.. TODO: Complete this part + By the tutor plugin X + ~~~~~~~~~~~~~~~~~~~~~~ + You can generate and send reports automatically by installing the tutor plugin X and following its instructions. + +Django Admin +~~~~~~~~~~~~~ +You can create reports using the Django Admin; for that, you need to follow these steps: + +1. Enter the **Survey Report** option in your Django admin (URL: ``/admin/survey_report/surveyreport/``) +2. Click the **Generate Report** button. +3. Then, you can select the reports you want to send and use the admin actions to send the report to an external API. + + .. image:: docs/_images/survey_report_admin.png + :alt: Survey report by Django admin + +Screenshot of Survey Report option in a Django admin and use the admin actions to send the report to an external API + +Command Line +~~~~~~~~~~~~~ +1. Run a Bash shell in your LMS container. For example, using ``tutor dev run lms bash``. +2. Run the command: ``./manage.py lms generate_report`` + +**Note:** by default that the command also sends the report; if you only want to generate it, you need to add the flag ``--no-send``. For more information, you can run the command ``./manage.py lms generate_report --help`` + + .. image:: docs/_images/survey_report_command.png + :alt: Survey Report by command line + +Screenshot of a bash shell with the result of running ``./manage.py lms generate_report --no-send`` + +Settings for Survey Report +---------------------------- + +You have the following settings to customize the behavior of your reports. + +- ``SURVEY_REPORT_EXTRA_DATA``: This setting is a dictionary. This info will appear as a value in the report extra_data attribute. By default, the value is {}. + +- ``SURVEY_REPORT_ENDPOINT``: This setting is a string with the endpoint to send the report. This URL should be capable of receiving a POST request with the data. By default, the setting is to an Open edX organization endpoint. + +- ``ANONYMOUS_SURVEY_REPORT``: This is a boolean to specify if you want to use your LMS domain as ID for your report or to send the information anonymously with a UUID. By default, this setting is False. + +- ``SURVEY_REPORT_ENABLE``: This is a boolean to specify if you want to enable or disable the survey report feature completely. The banner will disappear and the report generation will be disabled if set to False. By default, this setting is True. + +About the Survey Report Admin Banner +------------------------------------- + +This app implements a banner to make it easy for the Open edX operators to generate and send reports automatically. + + .. image:: docs/_images/survey_report_banner.png + :alt: Survey Report Banner + +**Note:** The banner will appear if a survey report is not sent in the months defined in the ``context_processor`` file, by default, is set to appear every 6 months. diff --git a/openedx/features/survey_report/admin.py b/openedx/features/survey_report/admin.py index f2a422de2ef8..a5719d966a93 100644 --- a/openedx/features/survey_report/admin.py +++ b/openedx/features/survey_report/admin.py @@ -4,6 +4,7 @@ from django.contrib import admin +from django.conf import settings from .models import SurveyReport from .api import send_report_to_external_api @@ -21,7 +22,7 @@ class SurveyReportAdmin(admin.ModelAdmin): ) list_display = ( - 'id', 'summary', 'created_at', 'state' + 'id', 'summary', 'created_at', 'report_state' ) actions = ['send_report'] @@ -80,4 +81,18 @@ def get_actions(self, request): del actions['delete_selected'] return actions -admin.site.register(SurveyReport, SurveyReportAdmin) + def report_state(self, obj): + """ + Method to define the custom State column with the new "send" state, + to avoid modifying the current models. + """ + try: + if obj.surveyreportupload_set.last().is_uploaded(): + return "Sent" + except AttributeError: + return obj.state.capitalize() + report_state.short_description = 'State' + + +if settings.SURVEY_REPORT_ENABLE: + admin.site.register(SurveyReport, SurveyReportAdmin) diff --git a/openedx/features/survey_report/api.py b/openedx/features/survey_report/api.py index bce26b19b0b8..cb8eadc29f1e 100644 --- a/openedx/features/survey_report/api.py +++ b/openedx/features/survey_report/api.py @@ -45,6 +45,8 @@ def get_report_data() -> dict: def generate_report() -> None: """ Generate a report with relevant data.""" + if not settings.SURVEY_REPORT_ENABLE: + raise Exception("Survey report generation is not enabled") data = {} survey_report = SurveyReport(**data) survey_report.save() @@ -53,6 +55,7 @@ def generate_report() -> None: data = get_report_data() data["state"] = SURVEY_REPORT_GENERATED update_report(survey_report.id, data) + send_report_to_external_api(survey_report.id) except (Exception, ) as update_report_error: update_report(survey_report.id, {"state": SURVEY_REPORT_ERROR}) raise Exception(update_report_error) from update_report_error diff --git a/openedx/features/survey_report/context_processors.py b/openedx/features/survey_report/context_processors.py new file mode 100644 index 000000000000..f2e8892d84bc --- /dev/null +++ b/openedx/features/survey_report/context_processors.py @@ -0,0 +1,64 @@ +""" +This module provides context processors for integrating survey report functionality +into Django admin sites. + +It includes functions for determining whether to display a survey report banner and +calculating the date threshold for displaying the banner. + +Functions: +- admin_extra_context(request): +Sends extra context to every admin site, determining whether to display the +survey report banner based on defined settings and conditions. + +- should_show_survey_report_banner(): +Determines whether to show the survey report banner based on the threshold. + +- get_months_threshold(months): +Calculates the date threshold based on the specified number of months. + +Dependencies: +- Django: settings, reverse, shortcuts +- datetime: datetime +- dateutil.relativedelta: relativedelta + +Usage: +This module is designed to be imported into Django projects with admin functionality. +It enhances the admin interface by providing dynamic context for displaying a survey +report banner based on defined conditions and settings. +""" +from django.conf import settings +from django.urls import reverse +from datetime import datetime +from dateutil.relativedelta import relativedelta +from .models import SurveyReport + + +def admin_extra_context(request): + """ + This function sends extra context to every admin site. + The current threshold to show the banner is one month but this can be redefined in the future. + """ + if not settings.SURVEY_REPORT_ENABLE or not request.path.startswith(reverse('admin:index')): + return {'show_survey_report_banner': False} + + return {'show_survey_report_banner': should_show_survey_report_banner()} + + +def should_show_survey_report_banner(): + """ + Determine whether to show the survey report banner based on the threshold. + """ + months_threshold = get_months_threshold(settings.SURVEY_REPORT_CHECK_THRESHOLD) + + try: + latest_report = SurveyReport.objects.latest('created_at') + return latest_report.created_at.date() <= months_threshold + except SurveyReport.DoesNotExist: + return True + + +def get_months_threshold(months): + """ + Calculate the date threshold based on the specified number of months. + """ + return datetime.today().date() - relativedelta(months=months) diff --git a/openedx/features/survey_report/docs/_images/survey_report_admin.png b/openedx/features/survey_report/docs/_images/survey_report_admin.png new file mode 100644 index 000000000000..d179a5b498e0 Binary files /dev/null and b/openedx/features/survey_report/docs/_images/survey_report_admin.png differ diff --git a/openedx/features/survey_report/docs/_images/survey_report_banner.png b/openedx/features/survey_report/docs/_images/survey_report_banner.png new file mode 100644 index 000000000000..c21e44ad0766 Binary files /dev/null and b/openedx/features/survey_report/docs/_images/survey_report_banner.png differ diff --git a/openedx/features/survey_report/docs/_images/survey_report_command.png b/openedx/features/survey_report/docs/_images/survey_report_command.png new file mode 100644 index 000000000000..919877f9889e Binary files /dev/null and b/openedx/features/survey_report/docs/_images/survey_report_command.png differ 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 74204981d520..a1afddabe3f3 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 @@ -16,8 +16,9 @@ class GenerateReportTest(TestCase): Test for generate_report command. """ + @mock.patch('openedx.features.survey_report.api.send_report_to_external_api') @mock.patch('openedx.features.survey_report.api.get_report_data') - def test_generate_report(self, mock_get_report_data): + def test_generate_report(self, mock_get_report_data, mock_send_report): """ Test that generate_report command creates a survey report. """ @@ -30,6 +31,7 @@ def test_generate_report(self, mock_get_report_data): 'extra_data': {'extra': 'data'}, } mock_get_report_data.return_value = report_test_data + mock_send_report.return_value = None out = StringIO() call_command('generate_report', no_send=True, stdout=out) diff --git a/openedx/features/survey_report/static/survey_report/js/admin_banner.js b/openedx/features/survey_report/static/survey_report/js/admin_banner.js new file mode 100644 index 000000000000..d8650321ba36 --- /dev/null +++ b/openedx/features/survey_report/static/survey_report/js/admin_banner.js @@ -0,0 +1,37 @@ +$(document).ready(function(){ + $('#dismissButton').click(function() { + $('#originalContent').slideUp('slow', function() { + // If you want to do something after the slide-up, do it here. + // For example, you can hide the entire div: + // $(this).hide(); + }); + }); + // When the form is submitted + $("#survey_report_form").submit(function(event){ + event.preventDefault(); // Prevent the form from submitting traditionally + + // Make the AJAX request + $.ajax({ + url: $(this).attr("action"), + type: $(this).attr("method"), + data: $(this).serialize(), + success: function(response){ + // Hide the original content block + $("#originalContent").slideUp(400, function() { + //$(this).css('display', 'none'); + // Show the thank-you message block with slide down effect + $("#thankYouMessage").slideDown(400, function() { + // Wait for 3 seconds (3000 milliseconds) and then slide up the thank-you message + setTimeout(function() { + $("#thankYouMessage").slideUp(400); + }, 3000); + }); + }); + }, + error: function(error){ + // Handle any errors + console.error("Error sending report:", error); + } + }); + }); +}); diff --git a/openedx/features/survey_report/templates/survey_report/admin_banner.html b/openedx/features/survey_report/templates/survey_report/admin_banner.html new file mode 100644 index 000000000000..fa0b37ecf751 --- /dev/null +++ b/openedx/features/survey_report/templates/survey_report/admin_banner.html @@ -0,0 +1,36 @@ +{% block survey_report_banner %} +{% load static %} +{% if show_survey_report_banner %} +
+
+

Join the Open edX Data Sharing Initiative and shape the future of learning

+
+
+

The Open edX Project relies on the collective strength of its community to be a thriving platform for online education.

+

Open edX is a dynamic ecosystem and it is used in diverse learning environments. By sharing anonymized reports of aggregated data, you can contribute to the collective knowledge of the community. This data can help us all understand the reach of our project, make better decisions and ultimately support innovation in lifelong learning and advance next generation learning experience platforms.

+

We invite you to join the Open edX Data Sharing Initiative by sharing an anonymized reports of aggregated data from your institution's usage of the platform. The report data will be sent to Axim Collaborative, the non-profit behind the Open edX project.

+

If you agree and want to send a report you can click the button below. You can always send reports and see the status of reports you have sent in the past at admin/survey_report/surveyreport/ .

+
+
+ +
+ {% csrf_token %} + +
+
+
+ +{% endif %} + + + +{% endblock %} diff --git a/openedx/features/survey_report/templates/survey_report/change_list.html b/openedx/features/survey_report/templates/survey_report/change_list.html index c4c0ebcb6728..2cd947273e86 100644 --- a/openedx/features/survey_report/templates/survey_report/change_list.html +++ b/openedx/features/survey_report/templates/survey_report/change_list.html @@ -5,7 +5,7 @@
  • {% csrf_token %} - +