Commit f39833c7 by Ari Rizzitano

expand proctored exam report to include violation summary

EDUCATOR-411

hit new report endpoint
EDUCATOR-411

bad syntax

python join != js join

csv headings list too long for task

fix string formatting

fix wrong set of keys

bump edx-proctoring to 1.2.0
parent f79e9da0
...@@ -1457,20 +1457,9 @@ def get_proctored_exam_results(request, course_id): ...@@ -1457,20 +1457,9 @@ def get_proctored_exam_results(request, course_id):
""" """
get the proctored exam resultsreport for the particular course. get the proctored exam resultsreport for the particular course.
""" """
query_features = [
'user_email',
'exam_name',
'attempt_code',
'allowed_time_limit_mins',
'is_sample_attempt',
'started_at',
'completed_at',
'status',
]
course_key = CourseKey.from_string(course_id) course_key = CourseKey.from_string(course_id)
report_type = _('proctored exam results') report_type = _('proctored exam results')
lms.djangoapps.instructor_task.api.submit_proctored_exam_results_report(request, course_key, query_features) lms.djangoapps.instructor_task.api.submit_proctored_exam_results_report(request, course_key)
success_status = SUCCESS_MESSAGE_TEMPLATE.format(report_type=report_type) success_status = SUCCESS_MESSAGE_TEMPLATE.format(report_type=report_type)
return JsonResponse({"status": success_status}) return JsonResponse({"status": success_status})
......
...@@ -12,7 +12,7 @@ from django.core.exceptions import ObjectDoesNotExist ...@@ -12,7 +12,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db.models import Count, Q from django.db.models import Count, Q
from edx_proctoring.api import get_all_exam_attempts from edx_proctoring.api import get_exam_violation_report
from opaque_keys.edx.keys import UsageKey from opaque_keys.edx.keys import UsageKey
import xmodule.graders as xmgraders import xmodule.graders as xmgraders
...@@ -326,20 +326,30 @@ def get_proctored_exam_results(course_key, features): ...@@ -326,20 +326,30 @@ def get_proctored_exam_results(course_key, features):
""" """
Return info about proctored exam results in a course as a dict. Return info about proctored exam results in a course as a dict.
""" """
def extract_student(exam_attempt, features): comment_statuses = ['Rules Violation', 'Suspicious']
def extract_details(exam_attempt, features):
""" """
Build dict containing information about a single student exam_attempt. Build dict containing information about a single student exam_attempt.
""" """
proctored_exam = dict( proctored_exam = dict(
(feature, exam_attempt.get(feature)) for feature in features if feature in exam_attempt (feature, exam_attempt.get(feature)) for feature in features if feature in exam_attempt
) )
proctored_exam.update({'exam_name': exam_attempt.get('proctored_exam').get('exam_name')})
proctored_exam.update({'user_email': exam_attempt.get('user').get('email')}) for status in comment_statuses:
comment_list = exam_attempt.get(
'{status} Comments'.format(status=status),
[]
)
proctored_exam.update({
'{status} Count'.format(status=status): len(comment_list),
'{status} Comments'.format(status=status): '; '.join(comment_list),
})
return proctored_exam return proctored_exam
exam_attempts = get_all_exam_attempts(course_key) exam_attempts = get_exam_violation_report(course_key)
return [extract_student(exam_attempt, features) for exam_attempt in exam_attempts] return [extract_details(exam_attempt, features) for exam_attempt in exam_attempts]
def coupon_codes_features(features, coupons_list, course_id): def coupon_codes_features(features, coupons_list, course_id):
......
...@@ -238,13 +238,17 @@ class TestAnalyticsBasic(ModuleStoreTestCase): ...@@ -238,13 +238,17 @@ class TestAnalyticsBasic(ModuleStoreTestCase):
def test_get_student_exam_attempt_features(self): def test_get_student_exam_attempt_features(self):
query_features = [ query_features = [
'user_email', 'email',
'exam_name', 'exam_name',
'allowed_time_limit_mins', 'allowed_time_limit_mins',
'is_sample_attempt', 'is_sample_attempt',
'started_at', 'started_at',
'completed_at', 'completed_at',
'status', 'status',
'Suspicious Count',
'Suspicious Comments',
'Rules Violation Count',
'Rules Violation Comments',
] ]
proctored_exam_id = create_exam(self.course_key, 'Test Content', 'Test Exam', 1) proctored_exam_id = create_exam(self.course_key, 'Test Content', 'Test Exam', 1)
......
...@@ -431,7 +431,7 @@ def submit_course_survey_report(request, course_key): ...@@ -431,7 +431,7 @@ def submit_course_survey_report(request, course_key):
return submit_task(request, task_type, task_class, course_key, task_input, task_key) return submit_task(request, task_type, task_class, course_key, task_input, task_key)
def submit_proctored_exam_results_report(request, course_key, features): # pylint: disable=invalid-name def submit_proctored_exam_results_report(request, course_key): # pylint: disable=invalid-name
""" """
Submits a task to generate a HTML File containing the executive summary report. Submits a task to generate a HTML File containing the executive summary report.
...@@ -439,7 +439,7 @@ def submit_proctored_exam_results_report(request, course_key, features): # pyli ...@@ -439,7 +439,7 @@ def submit_proctored_exam_results_report(request, course_key, features): # pyli
""" """
task_type = 'proctored_exam_results_report' task_type = 'proctored_exam_results_report'
task_class = proctored_exam_results_csv task_class = proctored_exam_results_csv
task_input = {'features': features} task_input = {}
task_key = "" task_key = ""
return submit_task(request, task_type, task_class, course_key, task_input, task_key) return submit_task(request, task_type, task_class, course_key, task_input, task_key)
......
...@@ -100,7 +100,23 @@ def upload_proctored_exam_results_report(_xmodule_instance_args, _entry_id, cour ...@@ -100,7 +100,23 @@ def upload_proctored_exam_results_report(_xmodule_instance_args, _entry_id, cour
task_progress.update_task_state(extra_meta=current_step) task_progress.update_task_state(extra_meta=current_step)
# Compute result table and format it # Compute result table and format it
query_features = _task_input.get('features') query_features = [
'course_id',
'exam_name',
'username',
'email',
'attempt_code',
'allowed_time_limit_mins',
'is_sample_attempt',
'started_at',
'completed_at',
'status',
'review_status',
'Suspicious Count',
'Suspicious Comments',
'Rules Violation Count',
'Rules Violation Comments'
]
student_data = get_proctored_exam_results(course_id, query_features) student_data = get_proctored_exam_results(course_id, query_features)
header, rows = format_dictlist(student_data, query_features) header, rows = format_dictlist(student_data, query_features)
......
...@@ -96,7 +96,7 @@ git+https://github.com/edx/xblock-utils.git@v1.0.5#egg=xblock-utils==1.0.5 ...@@ -96,7 +96,7 @@ git+https://github.com/edx/xblock-utils.git@v1.0.5#egg=xblock-utils==1.0.5
-e git+https://github.com/edx-solutions/xblock-google-drive.git@138e6fa0bf3a2013e904a085b9fed77dab7f3f21#egg=xblock-google-drive -e git+https://github.com/edx-solutions/xblock-google-drive.git@138e6fa0bf3a2013e904a085b9fed77dab7f3f21#egg=xblock-google-drive
git+https://github.com/edx/edx-user-state-client.git@1.0.1#egg=edx-user-state-client==1.0.1 git+https://github.com/edx/edx-user-state-client.git@1.0.1#egg=edx-user-state-client==1.0.1
git+https://github.com/edx/xblock-lti-consumer.git@v1.1.5#egg=lti_consumer-xblock==1.1.5 git+https://github.com/edx/xblock-lti-consumer.git@v1.1.5#egg=lti_consumer-xblock==1.1.5
git+https://github.com/edx/edx-proctoring.git@1.1.0#egg=edx-proctoring==1.1.0 git+https://github.com/edx/edx-proctoring.git@1.2.0#egg=edx-proctoring==1.2.0
# Third Party XBlocks # Third Party XBlocks
git+https://github.com/open-craft/xblock-poll@7ba819b968fe8faddb78bb22e1fe7637005eb414#egg=xblock-poll==1.2.7 git+https://github.com/open-craft/xblock-poll@7ba819b968fe8faddb78bb22e1fe7637005eb414#egg=xblock-poll==1.2.7
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment