Skip to content

Commit

Permalink
Add enrollment activity report (#466)
Browse files Browse the repository at this point in the history
* feat: add enrollment activity report

* feat: remove unused imports

* chore: adds tests for enrollment activity report

---------

Co-authored-by: Ahmed-khalid9199 <[email protected]>
  • Loading branch information
ahmed-arb and Ahmed-khalid9199 authored Nov 7, 2024
1 parent 8e0a29e commit ed8f8f2
Show file tree
Hide file tree
Showing 12 changed files with 349 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
task_all_courses_enrollment_report,
task_user_pref_lang_report,
task_users_enrollment_info_report,
task_enrollment_activity_report
)
from openedx.features.wikimedia_features.admin_dashboard.course_versions import task_helper

Expand Down Expand Up @@ -257,6 +258,27 @@ def users_enrollment_report(request):
submit_task(request, report_type, task_users_enrollment_info_report, 'all_courses', task_input, "")
return JsonResponse({"status": success_status})

@transaction.non_atomic_requests
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def enrollment_activity_report(request):
"""
Handles request to generate CSV of all users enrollments detailed info. This report has a separate row
for each enrollment.
"""
report_type = _("enrollment_acctivity_report")
success_status = SUCCESS_MESSAGE_TEMPLATE.format(
report_type="User Enrollments Expanded Report"
)
task_input = {
'features': ["username", "course_title", "enrollment_date", "completion_date"],
'csv_type': report_type,
}

submit_task(request, report_type, task_enrollment_activity_report, 'all_courses', task_input, "")
return JsonResponse({"status": success_status})


def submit_average_calculate_grades_csv(request, course_key):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def section_data_download(course, access):
'all_courses_enrollments_csv_url': reverse('admin_dashboard:all_courses_enrollment_report'),
'user_pref_lang_csv_url': reverse('admin_dashboard:user_pref_lang_report'),
'users_enrollment_url': reverse('admin_dashboard:users_enrollment_report'),
'enrollment_activity_url': reverse('admin_dashboard:enrollment_activity_report'),
}
if not (access.get('data_researcher') or access.get('staff') or access.get('instructor')):
section_data['is_hidden'] = True
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
list_all_courses_enrollment_data,
list_user_pref_lang,
list_users_enrollments,
list_enrollment_activity,
list_version_report_info_per_course,
list_version_report_info_total,
list_quarterly_courses_enrollment_data,
Expand Down Expand Up @@ -293,3 +294,43 @@ def upload_users_enrollment_info_csv(
report_store.store_rows(course_id_str, report_name, rows)

return task_progress.update_task_state(extra_meta=current_step)


def upload_enrollment_activity_csv(
_xmodule_instance_args, _entry_id, course_id_str, task_input, action_name, user_ids
):
"""
Generate a CSV file containing information of course enrollments and completion dates.
"""
start_time = time()
start_date = datetime.now(UTC)
num_reports = 1
task_progress = TaskProgress(action_name, num_reports, start_time)
current_step = {"step": "Getting enrollments"}
task_progress.update_task_state(extra_meta=current_step)

# Compute result table and format it
query_features = task_input.get("features")

query_features_names = ["User", "Course Title", "Enrollment Date", "Completion Date"]

data = list_enrollment_activity()
__, rows = format_dictlist(data, query_features)

task_progress.attempted = task_progress.succeeded = len(rows)
task_progress.skipped = task_progress.total - task_progress.attempted

rows.insert(0, query_features_names)

current_step = {"step": "Uploading CSV"}
task_progress.update_task_state(extra_meta=current_step)

# Perform the upload
report_store = ReportStore.from_config("GRADES_DOWNLOAD")
csv_name = task_input.get("csv_type")
report_name = "{csv_name}_{timestamp_str}.csv".format(
csv_name=csv_name, timestamp_str=start_date.strftime("%Y-%m-%d-%H%M")
)
report_store.store_rows(course_id_str, report_name, rows)

return task_progress.update_task_state(extra_meta=current_step)
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@
from django.conf import settings
from django.db.models import OuterRef, Subquery, Value, TextField
from django.db.models.functions import Coalesce
from django.db.models import Prefetch

from common.djangoapps.student.models import CourseEnrollment

from lms.djangoapps.branding import get_visible_courses
from lms.djangoapps.courseware.courses import get_course_by_id
from lms.djangoapps.grades.api import CourseGradeFactory

Expand All @@ -19,7 +17,7 @@
from openedx.core.djangoapps.user_api.models import UserPreference
from openedx.features.wikimedia_features.meta_translations.models import CourseTranslation
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.features.wikimedia_features.wikimedia_general.utils import get_course_enrollment_and_completion_stats, get_user_course_completions
from openedx.features.wikimedia_features.wikimedia_general.utils import get_course_completion_date, get_course_enrollment_and_completion_stats, get_user_course_completions

from edx_proctoring.api import get_last_exam_completion_date

Expand Down Expand Up @@ -366,12 +364,15 @@ def list_user_pref_lang():

return pref_lang_data

def get_users_with_enrollments():
return User.objects.prefetch_related("courseenrollment_set__course").filter(
courseenrollment__is_active=1
).distinct()


def list_users_enrollments():

users_with_course_enrollments = User.objects.prefetch_related("courseenrollment_set__course").filter(
courseenrollment__is_active=1
).distinct()
users_with_course_enrollments = get_users_with_enrollments()

users_enrollments_data = []

Expand All @@ -389,3 +390,33 @@ def list_users_enrollments():
)

return users_enrollments_data


def list_enrollment_activity():
date_format = "%Y-%m-%d"
users_with_course_enrollments = get_users_with_enrollments()

enrollments_activity_data = []

for user in users_with_course_enrollments:
user_enrollments = user.courseenrollment_set.all()
for enrollment in user_enrollments:
log.info("find me: " + str(enrollment.id))
try:
course = enrollment.course
except CourseOverview.DoesNotExist:
log.info(f"CourseOverview with ID {enrollment.course_id} does not exist")
continue
except Exception as e:
raise e
course_completion_date = get_course_completion_date(user, course.id)
enrollments_activity_data.append(
{
"username": user.username,
"course_title": course.display_name,
"enrollment_date": enrollment.created.strftime(date_format),
"completion_date": course_completion_date.strftime(date_format) if course_completion_date else 'N/A',
}
)

return enrollments_activity_data
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@
let quarter = $('#select-enrollment-quarter').val()
AjaxCall(url_for_courses_enrollment_report, {year: Number(year), quarter: Number(quarter)});
})
// TODO: (edly) combine similar triggers under one function
$("[name='all-courses-enrollments-csv']").click(function() {
let data_url = $(this).attr('data-endpoint');
AjaxCall(data_url);
Expand All @@ -332,6 +333,10 @@
let data_url = $(this).attr('data-endpoint');
AjaxCall(data_url);
})
$("[name='enrollment-activity-csv']").click(function() {
let data_url = $(this).attr('data-endpoint');
AjaxCall(data_url);
})
$(document).ready(function() {
endpoint = '/wikimedia/list_all_courses_report_downloads';
ReportDownloads();
Expand Down
19 changes: 19 additions & 0 deletions openedx/features/wikimedia_features/admin_dashboard/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
upload_all_courses_enrollment_csv,
upload_user_pref_lang_csv,
upload_users_enrollment_info_csv,
upload_enrollment_activity_csv,
)
from openedx.features.wikimedia_features.email.utils import send_notification

Expand Down Expand Up @@ -161,6 +162,24 @@ def task_users_enrollment_info_report(entry_id, xmodule_instance_args, user_id):
return run_main_task(entry_id, task_fn, action_name, user_id)


@shared_task(base=BaseAdminReportTask)
@set_code_owner_attribute
def task_enrollment_activity_report(entry_id, xmodule_instance_args, user_id):
"""
Generate a expanded report for users enrollments and course completion dates.
"""
# Translators: This is a past-tense verb that is inserted into task progress messages as {action}.
action_name = ugettext_noop("generated")
TASK_LOG.info(
"Task: %s, AdminReportTask ID: %s, Task type: %s, Preparing for task execution",
xmodule_instance_args.get("task_id"),
entry_id,
action_name,
)
task_fn = partial(upload_enrollment_activity_csv, xmodule_instance_args)
return run_main_task(entry_id, task_fn, action_name, user_id)


@task(base=LoggedTask)
def send_report_ready_email_task(msg_class_key, context, subject, email):
TASK_LOG.info("Initiated task to send admin report ready notifications.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@
>${_("Generate")}</button>
</td>
</tr>
<tr class="all-courses-report">
<td>
<span class="text">${_("Enrollment activity report")}</span>
<span class="info-text">${_("Generates an expanded report of user's enrollments and course completion dates")}</span>
</td>
<td></td>
<td class="action">
<button
name="enrollment-activity-csv"
class="btn btn-primary"
value="${_("Generate Users Enrollment Report")}"
data-endpoint="${ section_data['key']['enrollment_activity_url'] }"
>${_("Generate")}</button>
</td>
</tr>
<tr class="single-course-report">
<td>
<span class="text">${_("List profile information for enrolled students")}</span>
Expand Down
Empty file.
Empty file.
Loading

0 comments on commit ed8f8f2

Please sign in to comment.