Skip to content
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

Backport to Quince: survey report changes #34044

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = {}
6 changes: 0 additions & 6 deletions lms/envs/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ##############
Expand Down
2 changes: 2 additions & 0 deletions lms/envs/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ########################
Expand Down
10 changes: 9 additions & 1 deletion lms/templates/admin/base_site.html
Original file line number Diff line number Diff line change
@@ -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 %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">{{ site_header|default:_('Django administration') }}</a></h1>
Expand All @@ -20,3 +20,11 @@ <h1 id="site-name"><a href="{% url 'admin:index' %}">{{ site_header|default:_('D


{% endblock %}

{% block extrahead %}
<script src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>
{% endblock %}

{% block messages %}{{ block.super }}
{% include "survey_report/admin_banner.html" %}
{% endblock %}
87 changes: 77 additions & 10 deletions openedx/features/survey_report/README.rst
Original file line number Diff line number Diff line change
@@ -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: ``<your LMs domain>/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.
19 changes: 17 additions & 2 deletions openedx/features/survey_report/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -21,7 +22,7 @@ class SurveyReportAdmin(admin.ModelAdmin):
)

list_display = (
'id', 'summary', 'created_at', 'state'
'id', 'summary', 'created_at', 'report_state'
)

actions = ['send_report']
Expand Down Expand Up @@ -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)
3 changes: 3 additions & 0 deletions openedx/features/survey_report/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand Down
64 changes: 64 additions & 0 deletions openedx/features/survey_report/context_processors.py
Original file line number Diff line number Diff line change
@@ -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)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand All @@ -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)

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{% block survey_report_banner %}
{% load static %}
{% if show_survey_report_banner %}
<div id="originalContent" style="border: 3px solid #06405d; margin-bottom: 50px; rgb(0 0 0 / 18%) 0px 3px 5px;">
<div style="background-color: #06405d;padding: 17px 37px;">
<h1 style="margin: 0; color: #FFFF; font-weight: 600;">Join the Open edX Data Sharing Initiative and shape the future of learning</h1>
</div>
<div style="padding: 17px 37px;">
<p>The Open edX Project relies on the collective strength of its community to be a thriving platform for online education.</p>
<p>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.</p>
<p>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.</p>
<p>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 <a href="/admin/survey_report/surveyreport/">admin/survey_report/surveyreport/</a> .</p>
</div>
<div style="display: flex; justify-content: flex-end; padding: 0 37px 17px;">
<button id="dismissButton" type="button" style="background-color:var(--close-button-bg); color: var(--button-fg); border: none; border-radius: 4px; padding: 10px 20px; margin-right: 10px; cursor: pointer;">Dismiss</button>
<form id='survey_report_form' method="POST" action="/survey_report/generate_report" style="margin: 0; padding: 0;">
{% csrf_token %}
<button type="submit" style="background-color: #377D4D; color: var(--button-fg); border: none; border-radius: 4px; padding: 10px 20px; cursor: pointer;">Send Report</button>
</form>
</div>
</div>
<div id="thankYouMessage" style="display: none; background-color: var(--darkened-bg); padding: 20px 40px; margin-bottom: 30px;box-shadow: rgb(0 0 0 / 18%) 0px 3px 5px;">
<div style="display: flex; align-items: center;">
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 24 24">
<g fill="#377D4D"><path d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2s10 4.477 10 10Z"></path>
<path d="M16.03 8.97a.75.75 0 0 1 0 1.06l-5 5a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 1 1 1.06-1.06l1.47 1.47l2.235-2.236L14.97 8.97a.75.75 0 0 1 1.06 0Z" fill="#FFF"></path>
</g>
</svg>
<span style="font-size: 16px; margin-left: 15px;">Thank you for your collaboration and support! Your contribution is greatly appreciated and will help us continue to improve.</span>
</div>
</div>
{% endif %}

<script src="{% static 'survey_report/js/admin_banner.js' %}"></script>

{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<li>
<form method="POST" action="{% url 'openedx.generate_survey_report' %}" class="inline">
{% csrf_token %}
<input type="submit" value="Generate Report" class="default" name="_generatereport">
<input type="submit" value="Generate and Send Report" class="default" name="_sendreport">
</form>
</li>
</ul>
Expand Down
Loading