feat: add generate report button in admin (#31429)

* feat: add generate report button in admin
This commit is contained in:
Alejandro Cardenas
2023-01-25 10:19:59 -05:00
committed by GitHub
parent 10e06e792d
commit 045ae44184
11 changed files with 212 additions and 39 deletions

View File

@@ -665,6 +665,12 @@ urlpatterns += [
'u/',
include('openedx.features.learner_profile.urls'),
),
# Survey Report
re_path(
fr'^survey_report/',
include('openedx.features.survey_report.urls'),
),
]
if settings.FEATURES.get('ENABLE_TEAMS'):

View File

@@ -11,14 +11,16 @@ class SurveyReportAdmin(admin.ModelAdmin):
"""
Admin to manage survey reports.
"""
change_list_template = "survey_report/change_list.html"
readonly_fields = (
'courses_offered', 'learners', 'registered_learners',
'enrollments', 'generated_certificates', 'extra_data',
'created_at'
'created_at', 'state',
)
list_display = (
'id', 'summary', 'created_at'
'id', 'summary', 'created_at', 'state'
)
def summary(self, obj) -> str:

View File

@@ -12,12 +12,13 @@ from openedx.features.survey_report.queries import (
get_registered_learners,
get_unique_courses_offered
)
from .models import SURVEY_REPORT_ERROR, SURVEY_REPORT_GENERATED
MAX_WEEKS_SINCE_LAST_LOGIN: int = 4
def generate_report() -> None:
""" Generate a report with relevant data."""
def get_report_data() -> dict:
""" Get data from database to generate a new report."""
courses_offered = get_unique_courses_offered()
learners = get_recently_active_users(weeks=MAX_WEEKS_SINCE_LAST_LOGIN)
registered_learners = get_registered_learners()
@@ -25,13 +26,30 @@ def generate_report() -> None:
enrollments = get_course_enrollments()
extra_data = settings.SURVEY_REPORT_EXTRA_DATA
survey_report = SurveyReport(
courses_offered=courses_offered,
learners=learners,
registered_learners=registered_learners,
generated_certificates=certificates,
enrollments=enrollments,
extra_data=extra_data,
)
return {
"courses_offered": courses_offered,
"learners": learners,
"registered_learners": registered_learners,
"generated_certificates": certificates,
"enrollments": enrollments,
"extra_data": extra_data,
}
def generate_report() -> None:
""" Generate a report with relevant data."""
data = {}
survey_report = SurveyReport(**data)
survey_report.save()
try:
data = get_report_data()
data["state"] = SURVEY_REPORT_GENERATED
update_report(survey_report.id, data)
except (Exception, ) as update_report_error:
update_report(survey_report.id, {"state": SURVEY_REPORT_ERROR})
raise Exception(update_report_error) from update_report_error
def update_report(survey_report_id: int, data: dict) -> None:
SurveyReport.objects.filter(id=survey_report_id).update(**data)

View File

@@ -28,4 +28,6 @@ class Command(BaseCommand):
except Exception as error:
raise CommandError(f'An error has occurred while survey report was generating. {error}') from error
self.stdout.write(self.style.SUCCESS('Survey report has been generated successfully.'))
self.stdout.write(
self.style.SUCCESS('Survey report has been generated successfully.')
)

View File

@@ -6,7 +6,7 @@ from io import StringIO
from unittest import mock
from django.core.management import call_command
from django.test import TestCase, override_settings
from django.test import TestCase
from openedx.features.survey_report.models import SurveyReport
@@ -15,30 +15,29 @@ class GenerateReportTest(TestCase):
"""
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_registered_learners')
@mock.patch('openedx.features.survey_report.queries.get_recently_active_users')
@mock.patch('openedx.features.survey_report.queries.get_unique_courses_offered')
def test_generate_report(self, mock_get_unique_courses_offered, mock_get_recently_active_users,
mock_get_registered_learners, mock_get_generated_certificates,
mock_get_course_enrollments):
@mock.patch('openedx.features.survey_report.api.get_report_data')
def test_generate_report(self, mock_get_report_data):
"""
Test that generate_report command creates a survey report.
"""
mock_get_unique_courses_offered.return_value = 1
mock_get_recently_active_users.return_value = 2
mock_get_registered_learners.return_value = 3
mock_get_generated_certificates.return_value = 4
mock_get_course_enrollments.return_value = 5
report_test_data = {
'courses_offered': 1,
'learners': 2,
'registered_learners': 3,
'generated_certificates': 4,
'enrollments': 5,
'extra_data': {'extra': 'data'},
}
mock_get_report_data.return_value = report_test_data
out = StringIO()
call_command('generate_report', stdout=out)
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'}
assert survey_report.courses_offered == report_test_data['courses_offered']
assert survey_report.learners == report_test_data['learners']
assert survey_report.registered_learners == report_test_data['registered_learners']
assert survey_report.generated_certificates == report_test_data['generated_certificates']
assert survey_report.enrollments == report_test_data['enrollments']
assert survey_report.extra_data == report_test_data['extra_data']

View File

@@ -0,0 +1,43 @@
# Generated by Django 3.2.16 on 2022-12-11 15:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('survey_report', '0002_auto_20221130_1533'),
]
operations = [
migrations.AddField(
model_name='surveyreport',
name='state',
field=models.CharField(choices=[('processing', 'Processing'), ('generated', 'Generated'), ('error', 'Error')], default='processing', help_text='State of the async generating process.', max_length=24),
),
migrations.AlterField(
model_name='surveyreport',
name='courses_offered',
field=models.BigIntegerField(default=0, help_text='Total number of active unique courses.'),
),
migrations.AlterField(
model_name='surveyreport',
name='enrollments',
field=models.BigIntegerField(default=0, help_text='Total number of active enrollments in the platform.'),
),
migrations.AlterField(
model_name='surveyreport',
name='generated_certificates',
field=models.BigIntegerField(default=0, help_text='Total number of generated certificates.'),
),
migrations.AlterField(
model_name='surveyreport',
name='learners',
field=models.BigIntegerField(default=0, help_text='Total number of recently active users with login in some weeks.'),
),
migrations.AlterField(
model_name='surveyreport',
name='registered_learners',
field=models.BigIntegerField(default=0, help_text='Total number of users ever registered in the platform.'),
),
]

View File

@@ -5,6 +5,16 @@ Survey Report models.
from django.db import models
from jsonfield import JSONField
SURVEY_REPORT_PROCESSING = 'processing'
SURVEY_REPORT_GENERATED = 'generated'
SURVEY_REPORT_ERROR = 'error'
SURVEY_REPORT_STATES = [
(SURVEY_REPORT_PROCESSING, 'Processing'),
(SURVEY_REPORT_GENERATED, 'Generated'),
(SURVEY_REPORT_ERROR, 'Error'),
]
class SurveyReport(models.Model):
"""
@@ -19,18 +29,31 @@ class SurveyReport(models.Model):
- 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.
"""
courses_offered = models.BigIntegerField(help_text="Total number of active unique courses.")
learners = models.BigIntegerField(help_text="Total number of recently active users with login in some weeks.")
registered_learners = models.BigIntegerField(help_text="Total number of users ever registered in the platform.")
enrollments = models.BigIntegerField(help_text="Total number of active enrollments in the platform.")
generated_certificates = models.BigIntegerField(help_text="Total number of generated certificates.")
courses_offered = models.BigIntegerField(default=0, help_text="Total number of active unique courses.")
learners = models.BigIntegerField(
default=0,
help_text="Total number of recently active users with login in some weeks."
)
registered_learners = models.BigIntegerField(
default=0,
help_text="Total number of users ever registered in the platform."
)
enrollments = models.BigIntegerField(default=0, help_text="Total number of active enrollments in the platform.")
generated_certificates = models.BigIntegerField(default=0, help_text="Total number of generated certificates.")
extra_data = JSONField(
blank=True,
default=dict,
help_text="Extra information that will be saved in the report, E.g: site_name, openedx-release.",
)
created_at = models.DateTimeField(auto_now=True)
state = models.CharField(
max_length=24,
choices=SURVEY_REPORT_STATES,
default=SURVEY_REPORT_PROCESSING,
help_text="State of the async generating process."
)
class Meta:
ordering = ["-created_at"]

View File

@@ -0,0 +1,27 @@
"""
Tasks for Survey Report.
"""
import logging
from celery import shared_task
from .api import generate_report
log = logging.getLogger('edx.celery.task')
@shared_task(name='openedx.features.survey_report.tasks.generate_survey_report')
def generate_survey_report():
"""
Tasks to generate a new survey report with non-sensitive data.
"""
log.info(
'Started - generate survey report'
)
try:
generate_report()
log.info('Done - generate survey report')
except (Exception, ): # pylint: disable=broad-except
log.error('Error - generate survey report')

View File

@@ -0,0 +1,12 @@
{% extends 'admin/change_list.html' %}
{% block object-tools %}
<ul class="object-tools">
<li>
<form method="POST" action="{% url 'openedx.generate_survey_report' %}" class="inline">
{% csrf_token %}
<input type="submit" value="Generate Report" class="default" name="_generatereport">
</form>
</li>
</ul>
{% endblock %}

View File

@@ -0,0 +1,14 @@
"""
Defines URLs for Survey Report.
"""
from django.urls import path
from .views import SurveyReportView
urlpatterns = [
path(
'generate_report',
SurveyReportView.as_view(),
name='openedx.generate_survey_report',
),
]

View File

@@ -0,0 +1,27 @@
"""
Views to manage the Survey Reports.
"""
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.generic import View
from .tasks import generate_survey_report
class SurveyReportView(View):
"""
View for Survey Reports.
"""
@method_decorator(login_required)
@method_decorator(ensure_csrf_cookie)
def post(self, _request):
"""
Generate a new survey report using the generate_report method in api.py
Arguments:
_request: HTTP request
"""
generate_survey_report.delay()
return redirect("admin:survey_report_surveyreport_changelist")