diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index 5db9d5ccfcc0ef009fc0a9269eea97ce1f0fe674..dc4790e281c738815825501fa0c7a1a6fb7dac55 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -56,7 +56,11 @@ from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, U from lms.djangoapps.bulk_email.models import BulkEmailFlag, CourseEmail, CourseEmailTemplate from lms.djangoapps.certificates.api import generate_user_certificates from lms.djangoapps.certificates.models import CertificateStatuses -from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory +from lms.djangoapps.certificates.tests.factories import ( + CertificateInvalidationFactory, + CertificateWhitelistFactory, + GeneratedCertificateFactory +) from lms.djangoapps.courseware.models import StudentModule from lms.djangoapps.courseware.tests.factories import ( BetaTesterFactory, @@ -68,6 +72,10 @@ from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase from lms.djangoapps.experiments.testutils import override_experiment_waffle_flag from lms.djangoapps.instructor.tests.utils import FakeContentTask, FakeEmail, FakeEmailInfo from lms.djangoapps.instructor.views.api import ( + _check_if_learner_on_allowlist, + _check_if_learner_on_blocklist, + _get_certificate_for_user, + _get_student_from_request_data, _split_input_list, common_exceptions_400, generate_unique_password, @@ -4362,3 +4370,176 @@ class TestBulkCohorting(SharedModuleStoreTestCase): self.verify_success_on_file_content( 'username,email,cohort\r\nfoo_username,bar_email,baz_cohort', mock_store_upload, mock_cohort_task ) + + +class TestInstructorCertificateExceptions(SharedModuleStoreTestCase): + """ + Tests for utility functions utilized in the Instructor Dashboard Certificates app. + """ + + def setUp(self): + super().setUp() + self.global_staff = GlobalStaffFactory() + self.course = CourseFactory.create() + self.user = UserFactory() + CourseEnrollment.enroll(self.user, self.course.id) + + def test_get_student_from_request_data(self): + """ + Test ability to retrieve a learner record using their username and course id + """ + student = _get_student_from_request_data({"user": self.user.username}, self.course.id) + + assert student.username == self.user.username + + def test_get_student_from_request_data_empty_username(self): + """ + Test that we receive an expected error when no learner's username or email is entered + """ + with pytest.raises(ValueError) as error: + _get_student_from_request_data({"user": ""}, self.course.id) + + assert str(error.value) == ( + 'Student username/email field is required and can not be empty. Kindly fill in username/email and then ' + 'press "Invalidate Certificate" button.' + ) + + def test_get_student_from_request_data_user_dne(self): + """ + Test to verify an expected error message is returned when attempting to retrieve a learner that does not exist + in the LMS. + """ + with pytest.raises(ValueError) as error: + _get_student_from_request_data({"user": "Neo"}, self.course.id) + + assert str(error.value) == "Neo does not exist in the LMS. Please check your spelling and retry." + + def test_get_student_from_request_data_user_not_enrolled(self): + """ + Test to verify an expected error message is returned when attempting to retrieve a learner that is not enrolled + in a course-run. + """ + new_course = CourseFactory.create() + + with pytest.raises(ValueError) as error: + _get_student_from_request_data({"user": self.user.username}, new_course.id) + + assert str(error.value) == ( + f"{self.user.username} is not enrolled in this course. Please check your spelling and retry." + ) + + def test_get_certificate_for_user(self): + """ + Test that attempts to retrieve a Certificate for a learner in a course-run. + """ + generated_certificate = GeneratedCertificateFactory.create( + user=self.user, + course_id=self.course.id, + mode='verified', + status=CertificateStatuses.downloadable, + ) + + retrieved_certificate = _get_certificate_for_user(self.course.id, self.user) + + assert retrieved_certificate.id == generated_certificate.id + assert retrieved_certificate.user == self.user + assert retrieved_certificate.course_id == self.course.id + + def test_get_certificate_for_user_no_certificate(self): + """ + Test to verify an expected error message is returned when attempting to retrieve a certificate for a learner + that does not exist yet. + """ + with pytest.raises(ValueError) as error: + _get_certificate_for_user(self.course.id, self.user) + + assert str(error.value) == ( + f"The student {self.user} does not have certificate for the course {self.course.id.course}. Kindly " + "verify student username/email and the selected course are correct and try again." + ) + + def test_check_if_learner_on_allowlist(self): + """ + Test to verify that no learner is returned if the learner does not have an active entry on the allowlist. + """ + result = _check_if_learner_on_allowlist(self.course.id, self.user) + + assert not result + + def test_check_if_learner_on_allowlist_allowlist_entry_exists(self): + """ + Test that verifies the correct result is returned if a learner has an active entry on the allowlist. + """ + CertificateWhitelistFactory.create( + course_id=self.course.id, + user=self.user + ) + + result = _check_if_learner_on_allowlist(self.course.id, self.user) + + assert result + + def test_check_if_learner_on_blocklist_no_cert(self): + """ + Test to verify that the correct result is returned if a learner does not currently have a certificate in a + course-run. + """ + result = _check_if_learner_on_blocklist(self.course.id, self.user) + + assert not result + + def test_check_if_learner_on_blocklist_with_cert(self): + """ + Test to verify that the correct result is returned if a learner has a certificate but does not have an active + entry on the blocklist. + """ + GeneratedCertificateFactory.create( + user=self.user, + course_id=self.course.id, + mode='verified', + status=CertificateStatuses.downloadable, + ) + + result = _check_if_learner_on_blocklist(self.course.id, self.user) + assert not result + + def test_check_if_learner_on_blocklist_blocklist_entry_exists(self): + """ + Test to verify that the correct result is returned if a learner has a Certificate and an active entry on + the blocklist. + """ + generated_certificate = GeneratedCertificateFactory.create( + user=self.user, + course_id=self.course.id, + mode='verified', + status=CertificateStatuses.downloadable, + ) + + CertificateInvalidationFactory.create( + generated_certificate=generated_certificate, + invalidated_by=self.global_staff, + active=True + ) + + result = _check_if_learner_on_blocklist(self.course.id, self.user) + assert result + + def test_check_if_learner_on_blocklist_inactive_blocklist_entry_exists(self): + """ + Test to verify that the correct result is returned if a learner has an inactive entry on the blocklist. + """ + generated_certificate = GeneratedCertificateFactory.create( + user=self.user, + course_id=self.course.id, + mode='verified', + status=CertificateStatuses.downloadable, + ) + + CertificateInvalidationFactory.create( + generated_certificate=generated_certificate, + invalidated_by=self.global_staff, + active=False + ) + + result = _check_if_learner_on_blocklist(self.course.id, self.user) + assert not result diff --git a/lms/djangoapps/instructor/tests/test_certificates.py b/lms/djangoapps/instructor/tests/test_certificates.py index c60c81f7c62913f81a4be3f6f4cf8c6b730745de..78ee8d136018ed612fd4b32f93da8d608a104bc4 100644 --- a/lms/djangoapps/instructor/tests/test_certificates.py +++ b/lms/djangoapps/instructor/tests/test_certificates.py @@ -743,6 +743,40 @@ class CertificateExceptionViewInstructorApiTest(SharedModuleStoreTestCase): 'Certificate exception (user={user}) does not exist in certificate white list.' \ ' Please refresh the page and try again.'.format(user=self.certificate_exception['user_name']) + def test_certificate_invalidation_already_exists(self): + """ + Test to confirm an error message is raised when generating a certificate exception for a learner that already + has an active certificate invalidation. + """ + # generate a certificate for the test learner in our course + generated_certificate = GeneratedCertificateFactory.create( + user=self.user, + course_id=self.course.id, + status=CertificateStatuses.downloadable, + mode='honor', + ) + + # create a certificate invalidation tied to the generated certificate + CertificateInvalidationFactory.create( + generated_certificate=generated_certificate, + invalidated_by=self.global_staff, + ) + + # attempt to add learner to the allowlist, expect an error + response = self.client.post( + self.url, + data=json.dumps(self.certificate_exception), + content_type='application/json', + REQUEST_METHOD='POST' + ) + + res_json = json.loads(response.content.decode('utf-8')) + assert response.status_code == 400 + assert res_json['message'] == ( + f"Student {self.user.username} is already on the certificate invalidation list and cannot be added to " + "the certificate exception list." + ) + @override_settings(CERT_QUEUE='certificates') @ddt.ddt @@ -997,6 +1031,30 @@ class TestCertificatesInstructorApiBulkWhiteListExceptions(SharedModuleStoreTest assert len(data['general_errors']) == 1 assert len(data['success']) == 0 + def test_certificate_invalidation_already_exists(self): + """ + Test to confirm an error message is raised when generating a certificate exception for a learner appears in the + CSV file who has an active certificate invalidation. + """ + # generate a certificate for the test learner in our course + generated_certificate = GeneratedCertificateFactory.create( + user=self.enrolled_user_1, + course_id=self.course.id, + status=CertificateStatuses.downloadable, + mode='honor', + ) + + CertificateInvalidationFactory.create( + generated_certificate=generated_certificate, + invalidated_by=self.global_staff, + ) + + # attempt to add learner to the allowlist, expect an error + csv_content = b"test_student1@example.com,notes" + data = self.upload_file(csv_content=csv_content) + assert len(data['row_errors']['user_on_certificate_invalidation_list']) == 1 + assert data['row_errors']['user_on_certificate_invalidation_list'][0] == 'user "TestStudent1" in row# 1' + def upload_file(self, csv_content): """ Upload a csv file. @@ -1272,3 +1330,26 @@ class CertificateInvalidationViewTests(SharedModuleStoreTestCase): # Assert Error Message assert res_json['message'] == 'Certificate Invalidation does not exist, Please refresh the page and try again.' + + def test_learner_already_on_certificate_exception_list(self): + """ + Test to make sure we don't allow a single to learner to appear on both the certificate exception and + invalidation lists. + """ + # add test learner to the allowlist + CertificateWhitelistFactory.create(user=self.enrolled_user_1, course_id=self.course.id) + + # now try and add them to the invalidation list, expect an error + response = self.client.post( + self.url, + data=json.dumps(self.certificate_invalidation_data), + content_type='application/json', + ) + + res_json = json.loads(response.content.decode('utf-8')) + assert response.status_code == 400 + assert res_json['message'] == ( + f"The student {self.enrolled_user_1.username} appears on the Certificate Exception list in course " + f"{self.course.id}. Please remove them from the Certificate Exception list before attempting to " + "invalidate their certificate." + ) diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 90d005fa7ef81f36d3aee3695717b1879ea90451..0008b01312e782ba8d152d0178bae8f06ac280a6 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -2647,13 +2647,20 @@ def certificate_exception_view(request, course_id): def add_certificate_exception(course_key, student, certificate_exception): """ Add a certificate exception to CertificateWhitelist table. - Raises ValueError in case Student is already white listed. + + Raises ValueError in case Student is already white listed or if they appear on the block list. :param course_key: identifier of the course whose certificate exception will be added. :param student: User object whose certificate exception will be added. :param certificate_exception: A dict object containing certificate exception info. :return: CertificateWhitelist item in dict format containing certificate exception info. """ + if _check_if_learner_on_blocklist(course_key, student): + raise ValueError( + _("Student {user} is already on the certificate invalidation list and cannot be added to the certificate " + "exception list.").format(user=student.username) + ) + if CertificateWhitelist.get_certificate_white_list(course_key, student): raise ValueError( _("Student (username/email={user}) already in certificate exception list.").format(user=student.username) @@ -2667,7 +2674,8 @@ def add_certificate_exception(course_key, student, certificate_exception): 'notes': certificate_exception.get('notes', '') } ) - log.info('%s has been added to the whitelist in course %s', student.username, course_key) + + log.info(f"Student {student.id} has been added to the whitelist in course {course_key}") generated_certificate = GeneratedCertificate.eligible_certificates.filter( user=student, @@ -2835,17 +2843,24 @@ def generate_bulk_certificate_exceptions(request, course_id): { general_errors: [errors related to csv file e.g. csv uploading, csv attachment, content reading etc. ], row_errors: { - data_format_error: [users/data in csv file that are not well formatted], - user_not_exist: [csv with none exiting users in LMS system], - user_already_white_listed: [users that are already white listed], - user_not_enrolled: [rows with not enrolled users in the given course] + data_format_error: [users/data in csv file that are not well formatted], + user_not_exist: [csv with none exiting users in LMS system], + user_already_white_listed: [users that are already white listed], + user_not_enrolled: [rows with not enrolled users in the given course], + user_on_certificate_invalidation_list: [users that are currently have an active blocklist entry] }, success: [list of successfully added users to the certificate white list model] } """ user_index = 0 notes_index = 1 - row_errors_key = ['data_format_error', 'user_not_exist', 'user_already_white_listed', 'user_not_enrolled'] + row_errors_key = [ + 'data_format_error', + 'user_not_exist', + 'user_already_white_listed', + 'user_not_enrolled', + 'user_on_certificate_invalidation_list' + ] course_key = CourseKey.from_string(course_id) students, general_errors, success = [], [], [] row_errors = {key: [] for key in row_errors_key} @@ -2864,7 +2879,6 @@ def generate_bulk_certificate_exceptions(request, course_id): else: general_errors.append(_('Make sure that the file you upload is in CSV format with no ' 'extraneous characters or rows.')) - except Exception: # pylint: disable=broad-except general_errors.append(_('Could not read uploaded file.')) finally: @@ -2878,7 +2892,7 @@ def generate_bulk_certificate_exceptions(request, course_id): if len(student) != 2: if student: build_row_errors('data_format_error', student[user_index], row_num) - log.info('invalid data/format in csv row# %s', row_num) + log.info(f'Invalid data/format in csv row# {row_num}') continue user = student[user_index] @@ -2886,17 +2900,21 @@ def generate_bulk_certificate_exceptions(request, course_id): user = get_user_by_username_or_email(user) except ObjectDoesNotExist: build_row_errors('user_not_exist', user, row_num) - log.info('student %s does not exist', user) + log.info(f'Student {user} does not exist') else: - if CertificateWhitelist.get_certificate_white_list(course_key, user): + # make sure learner isn't on the blocklist + if _check_if_learner_on_blocklist(course_key, user): + build_row_errors('user_on_certificate_invalidation_list', user, row_num) + log.warning(f'Student {user.id} is blocked from receiving a Certificate in Course ' + f'{course_key}') + # make sure user isn't already on the exception list + elif CertificateWhitelist.get_certificate_white_list(course_key, user): build_row_errors('user_already_white_listed', user, row_num) - log.warning('student %s already exist.', user.username) - + log.warning(f'Student {user.id} already on exception list in Course {course_key}.') # make sure user is enrolled in course elif not CourseEnrollment.is_enrolled(user, course_key): build_row_errors('user_not_enrolled', user, row_num) - log.warning('student %s is not enrolled in course.', user.username) - + log.warning(f'Student {user.id} is not enrolled in Course {course_key}') else: CertificateWhitelist.objects.create( user=user, @@ -2905,7 +2923,6 @@ def generate_bulk_certificate_exceptions(request, course_id): notes=student[notes_index] ) success.append(_('user "{username}" in row# {row}').format(username=user.username, row=row_num)) - else: general_errors.append(_('File is not attached.')) @@ -2914,7 +2931,6 @@ def generate_bulk_certificate_exceptions(request, course_id): 'row_errors': row_errors, 'success': success } - return JsonResponse(results) @@ -2935,13 +2951,23 @@ def certificate_invalidation_view(request, course_id): # Validate request data and return error response in case of invalid data try: certificate_invalidation_data = parse_request_data(request) - certificate = validate_request_data_and_get_certificate(certificate_invalidation_data, course_key) + student = _get_student_from_request_data(certificate_invalidation_data, course_key) + certificate = _get_certificate_for_user(course_key, student) except ValueError as error: return JsonResponse({'message': str(error)}, status=400) # Invalidate certificate of the given student for the course course if request.method == 'POST': try: + if _check_if_learner_on_allowlist(course_key, student): + log.warning(f"Invalidating certificate for student {student.id} in course {course_key} failed. " + "Student is currently on the allow list.") + raise ValueError( + _("The student {student} appears on the Certificate Exception list in course {course}. Please " + "remove them from the Certificate Exception list before attempting to invalidate their " + "certificate.").format(student=student, course=course_key) + ) + certificate_invalidation = invalidate_certificate(request, certificate, certificate_invalidation_data) except ValueError as error: return JsonResponse({'message': str(error)}, status=400) @@ -3031,48 +3057,77 @@ def re_validate_certificate(request, course_key, generated_certificate): ) -def validate_request_data_and_get_certificate(certificate_invalidation, course_key): +def _get_boolean_param(request, param_name): + """ + Returns the value of the boolean parameter with the given + name in the POST request. Handles translation from string + values to boolean values. """ - Fetch and return GeneratedCertificate of the student passed in request data for the given course. + return request.POST.get(param_name, False) in ['true', 'True', True] - Raises ValueError in case of missing student username/email or - if student does not have certificate for the given course. - :param certificate_invalidation: dict containing certificate invalidation data - :param course_key: CourseKey object identifying the current course. - :return: GeneratedCertificate object of the student for the given course +def _create_error_response(request, msg): + """ + Creates the appropriate error response for the current request, + in JSON form. """ - user = certificate_invalidation.get("user") + return JsonResponse({"error": msg}, 400) + + +def _get_student_from_request_data(request_data, course_key): + """ + Attempts to retrieve the student information from the incoming request data. + :param request: HttpRequest object + :param course_key: CourseKey object identifying the current course. + """ + user = request_data.get("user") if not user: raise ValueError( _('Student username/email field is required and can not be empty. ' 'Kindly fill in username/email and then press "Invalidate Certificate" button.') ) - student = get_student(user, course_key) + return get_student(user, course_key) + +def _get_certificate_for_user(course_key, student): + """ + Attempts to retrieve a Certificate for a learner in a given course run key. + """ + log.info(f"Retrieving certificate for student {student.id} in course {course_key}") certificate = GeneratedCertificate.certificate_for_student(student, course_key) if not certificate: raise ValueError(_( "The student {student} does not have certificate for the course {course}. Kindly verify student " - "username/email and the selected course are correct and try again." - ).format(student=student.username, course=course_key.course)) + "username/email and the selected course are correct and try again.").format( + student=student.username, course=course_key.course) + ) + return certificate -def _get_boolean_param(request, param_name): +def _check_if_learner_on_allowlist(course_key, student): """ - Returns the value of the boolean parameter with the given - name in the POST request. Handles translation from string - values to boolean values. + Utility method that will try to determine if the learner is currently on the allowlist. This is a check that + occurs as part of adding a learner to the CertificateInvalidation list. """ - return request.POST.get(param_name, False) in ['true', 'True', True] + log.info(f"Checking if student {student.id} is currently on the allowlist of course {course_key}") + return CertificateWhitelist.objects.filter(user=student, course_id=course_key, whitelist=True).exists() -def _create_error_response(request, msg): +def _check_if_learner_on_blocklist(course_key, student): """ - Creates the appropriate error response for the current request, - in JSON form. + Utility method that will try to determine if the learner is currently on the block list. This is a check that + occurs as part of adding a learner to the Allow list. + + The CertificateInvalidation model does not store a username or user id, just a reference to the id of the + invalidated Certificate. We check if the learner has a Certificate in the Course-Run and then use that to check if + the learner has an active entry on the block list. """ - return JsonResponse({"error": msg}, 400) + log.info(f"Checking if student {student.id} is currently on the blocklist of course {course_key}") + cert = GeneratedCertificate.certificate_for_student(student, course_key) + if cert: + return CertificateInvalidation.objects.filter(generated_certificate_id=cert.id, active=True).exists() + + return False diff --git a/lms/static/js/certificates/views/certificate_bulk_whitelist.js b/lms/static/js/certificates/views/certificate_bulk_whitelist.js index 6e46cea5e90d81ce7f6493d711e2b5eb1e7c246a..be94068979faaa71c393cb015ba2c3503bd054e4 100644 --- a/lms/static/js/certificates/views/certificate_bulk_whitelist.js +++ b/lms/static/js/certificates/views/certificate_bulk_whitelist.js @@ -25,7 +25,8 @@ data_format_error: 'data-format-error', user_not_exist: 'user-not-exist', user_already_white_listed: 'user-already-white-listed', - user_not_enrolled: 'user-not-enrolled' + user_not_enrolled: 'user-not-enrolled', + user_on_certificate_invalidation_list: 'user-on-certificate-invalidation-list' }; return Backbone.View.extend({ @@ -43,7 +44,7 @@ render: function() { var template = this.loadTemplate('certificate-bulk-white-list'); - this.$el.html(template()); + this.$el.html(template()); // xss-lint: disable=javascript-jquery-html }, loadTemplate: function(name) { @@ -73,12 +74,91 @@ }, display_response: function(data_from_server) { + var UserOnCertificateInvalidationList; + $('.bulk-exception-results').removeClass('hidden').empty(); + function generateDiv(group, heading, displayData) { + // inner function generate div and display response messages. + $('<div/>', { + class: 'message ' + group + }).appendTo('.bulk-exception-results').prepend( // eslint-disable-line max-len, xss-lint: disable=javascript-jquery-insert-into-target,javascript-jquery-prepend + "<button type='button' id= '" + group + "' class='arrow'> + </button>" + heading) // eslint-disable-line max-len, xss-lint: disable=javascript-concat-html + .append($('<ul/>', { + class: group + })); + + for (var i = 0; i < displayData.length; i++) { // eslint-disable-line vars-on-top + $('<li/>', { + text: displayData[i] + }).appendTo('div.message > .' + group); // eslint-disable-line max-len, xss-lint: disable=javascript-jquery-insert-into-target + } + $('div.message > .' + group).hide(); + } + + function getDisplayText(qty, group) { + // inner function to display appropriate heading text + var text; + + switch (group) { + case MESSAGE_GROUP.successfully_added: + text = qty > 1 ? + gettext(qty + ' learners are successfully added to exception list') : + gettext(qty + ' learner is successfully added to the exception list'); + break; + + case MESSAGE_GROUP.data_format_error: + text = qty > 1 ? + gettext(qty + ' records are not in correct format and not added to' + + ' the exception list') : + gettext(qty + ' record is not in correct format and not added to the exception' + + ' list'); + break; + + case MESSAGE_GROUP.user_not_exist: + text = qty > 1 ? + gettext(qty + ' learners do not exist in LMS and not added to the' + + ' exception list') : + gettext(qty + ' learner does not exist in LMS and not added to the exception' + + ' list'); + break; + + case MESSAGE_GROUP.user_already_white_listed: + text = qty > 1 ? + gettext(qty + ' learners are already white listed and not added to' + + ' the exception list') : + gettext(qty + ' learner is already white listed and not added to the exception' + + ' list'); + break; + + case MESSAGE_GROUP.user_not_enrolled: + text = qty > 1 ? + gettext(qty + ' learners are not enrolled in course and not added to' + + ' the exception list') : + gettext(qty + ' learner is not enrolled in course and not added to the exception' + + ' list'); + break; + + case MESSAGE_GROUP.user_on_certificate_invalidation_list: + text = qty > 1 ? + gettext(qty + ' learners already appear on the certificate invalidation list') : + gettext(qty + ' learner already appears on the certificate invalidation list'); + break; + + default: + text = qty > 1 ? + gettext(qty + ' learners encountered unknown errors') : + gettext(qty + ' learner encountered an unknown error'); + break; + } + + return text; + } + // Display general error messages if (data_from_server.general_errors.length) { var errors = data_from_server.general_errors; - generate_div( + generateDiv( MESSAGE_GROUP.general_errors, gettext('Uploaded file issues. Click on "+" to view.'), errors @@ -88,9 +168,9 @@ // Display success message if (data_from_server.success.length) { var success_data = data_from_server.success; - generate_div( + generateDiv( MESSAGE_GROUP.successfully_added, - get_text(success_data.length, MESSAGE_GROUP.successfully_added), + getDisplayText(success_data.length, MESSAGE_GROUP.successfully_added), success_data ); } @@ -101,93 +181,51 @@ if (row_errors.data_format_error.length) { var format_errors = row_errors.data_format_error; - generate_div( + generateDiv( MESSAGE_GROUP.data_format_error, - get_text(format_errors.length, MESSAGE_GROUP.data_format_error), + getDisplayText(format_errors.length, MESSAGE_GROUP.data_format_error), format_errors ); } if (row_errors.user_not_exist.length) { var user_not_exist = row_errors.user_not_exist; - generate_div( + generateDiv( MESSAGE_GROUP.user_not_exist, - get_text(user_not_exist.length, MESSAGE_GROUP.user_not_exist), + getDisplayText(user_not_exist.length, MESSAGE_GROUP.user_not_exist), user_not_exist ); } if (row_errors.user_already_white_listed.length) { var user_already_white_listed = row_errors.user_already_white_listed; - generate_div( + generateDiv( MESSAGE_GROUP.user_already_white_listed, - get_text(user_already_white_listed.length, MESSAGE_GROUP.user_already_white_listed), + getDisplayText( + user_already_white_listed.length, + MESSAGE_GROUP.user_already_white_listed + ), user_already_white_listed ); } if (row_errors.user_not_enrolled.length) { var user_not_enrolled = row_errors.user_not_enrolled; - generate_div( + generateDiv( MESSAGE_GROUP.user_not_enrolled, - get_text(user_not_enrolled.length, MESSAGE_GROUP.user_not_enrolled), + getDisplayText(user_not_enrolled.length, MESSAGE_GROUP.user_not_enrolled), user_not_enrolled ); } - } - - function generate_div(group, heading, display_data) { - // inner function generate div and display response messages. - $('<div/>', { - class: 'message ' + group - }).appendTo('.bulk-exception-results').prepend( - "<button type='button' id= '" + group + "' class='arrow'> + </button>" + heading - ).append($('<ul/>', { - class: group - })); - - for (var i = 0; i < display_data.length; i++) { - $('<li/>', { - text: display_data[i] - }).appendTo('div.message > .' + group); - } - $('div.message > .' + group).hide(); - } - - function get_text(qty, group) { - // inner function to display appropriate heading text - var text; - switch (group) { - case MESSAGE_GROUP.successfully_added: - text = qty > 1 ? gettext(qty + ' learners are successfully added to exception list') : - gettext(qty + ' learner is successfully added to the exception list'); - break; - - case MESSAGE_GROUP.data_format_error: - text = qty > 1 ? gettext(qty + ' records are not in correct format and not added to' + - ' the exception list') : - gettext(qty + ' record is not in correct format and not added to the exception' + - ' list'); - break; - - case MESSAGE_GROUP.user_not_exist: - text = qty > 1 ? gettext(qty + ' learners do not exist in LMS and not added to the' + - ' exception list') : - gettext(qty + ' learner does not exist in LMS and not added to the exception list'); - break; - - case MESSAGE_GROUP.user_already_white_listed: - text = qty > 1 ? gettext(qty + ' learners are already white listed and not added to' + - ' the exception list') : - gettext(qty + ' learner is already white listed and not added to the exception ' + - 'list'); - break; - - case MESSAGE_GROUP.user_not_enrolled: - text = qty > 1 ? gettext(qty + ' learners are not enrolled in course and not added to' + - ' the exception list') : - gettext(qty + ' learner is not enrolled in course and not added to the exception' + - ' list'); - break; + if (row_errors.user_on_certificate_invalidation_list.length) { + UserOnCertificateInvalidationList = + row_errors.user_on_certificate_invalidation_list; + generateDiv( + MESSAGE_GROUP.user_on_certificate_invalidation_list, + getDisplayText( + UserOnCertificateInvalidationList.length, + MESSAGE_GROUP.user_on_certificate_invalidation_list + ), + UserOnCertificateInvalidationList + ); } - return text; } }, diff --git a/lms/static/js/spec/instructor_dashboard/certificates_bulk_exception_spec.js b/lms/static/js/spec/instructor_dashboard/certificates_bulk_exception_spec.js index 6be15fdae5c2c51f9009da8d7841a507ef8919b5..b1b8151f7e6b29bbe74704b3c9318c3fbf603402 100644 --- a/lms/static/js/spec/instructor_dashboard/certificates_bulk_exception_spec.js +++ b/lms/static/js/spec/instructor_dashboard/certificates_bulk_exception_spec.js @@ -74,7 +74,8 @@ define([ data_format_error: ['user 1 in row# 1'], user_not_exist: ['user 2 in row# 2'], user_already_white_listed: ['user 3 in row# 3'], - user_not_enrolled: ['user 4 in row# 4'] + user_not_enrolled: ['user 4 in row# 4'], + user_on_certificate_invalidation_list: ['user 5 in row# 5'] }, success: [] }); @@ -89,6 +90,9 @@ define([ expect($(SELECTORS.bulk_exception_results).text()).toContain('1 learner does not exist in LMS'); expect($(SELECTORS.bulk_exception_results).text()).toContain('1 learner is already white listed'); expect($(SELECTORS.bulk_exception_results).text()).toContain('1 learner is not enrolled in course'); + expect($(SELECTORS.bulk_exception_results).text()).toContain( + '1 learner already appears on the certificate invalidation list' + ); }); it('bind the ajax call and the result will be plural form of row errors', function() { @@ -100,7 +104,8 @@ define([ data_format_error: ['user 1 in row# 1', 'user 1 in row# 1'], user_not_exist: ['user 2 in row# 2', 'user 2 in row# 2'], user_already_white_listed: ['user 3 in row# 3', 'user 3 in row# 3'], - user_not_enrolled: ['user 4 in row# 4', 'user 4 in row# 4'] + user_not_enrolled: ['user 4 in row# 4', 'user 4 in row# 4'], + user_on_certificate_invalidation_list: ['user 5 in row# 5', 'user 5 in row# 5'] }, success: [] }); @@ -115,6 +120,9 @@ define([ expect($(SELECTORS.bulk_exception_results).text()).toContain('2 learners do not exist in LMS'); expect($(SELECTORS.bulk_exception_results).text()).toContain('2 learners are already white listed'); expect($(SELECTORS.bulk_exception_results).text()).toContain('2 learners are not enrolled in course'); + expect($(SELECTORS.bulk_exception_results).text()).toContain( + '2 learners already appear on the certificate invalidation list' + ); }); it('toggle message details', function() {