diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e50560a619f..234e9d1da10 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,11 @@ Change Log Unreleased ~~~~~~~~~~ +[3.19.0] - 2021-07-16 +~~~~~~~~~~~~~~~~~~~~~ +* Updated allowance modal to allow bulk allowances to be added. +* Added waffle flag to enable/disable bulk allowances feature. + [3.18.0] - 2021-07-15 ~~~~~~~~~~~~~~~~~~~~~ * Remove old proctored exam attempt url. diff --git a/edx_proctoring/__init__.py b/edx_proctoring/__init__.py index 93aebd9dc21..9856164995c 100644 --- a/edx_proctoring/__init__.py +++ b/edx_proctoring/__init__.py @@ -3,6 +3,6 @@ """ # Be sure to update the version number in edx_proctoring/package.json -__version__ = '3.18.0' +__version__ = '3.19.0' default_app_config = 'edx_proctoring.apps.EdxProctoringConfig' # pylint: disable=invalid-name diff --git a/edx_proctoring/api.py b/edx_proctoring/api.py index 49621f03728..2693de0736c 100644 --- a/edx_proctoring/api.py +++ b/edx_proctoring/api.py @@ -542,6 +542,8 @@ def add_bulk_allowances(exam_ids, user_ids, allowance_type, value): """ Adds (or updates) an allowance for multiple users and exams """ + + # Input pasrsing exam_ids = set(exam_ids) user_ids = set(user_ids) @@ -569,7 +571,7 @@ def add_bulk_allowances(exam_ids, user_ids, allowance_type, value): if multiplier < 0: raise AllowanceValueNotAllowedException(err_msg) - if allowance_type == constants.ADDITIONAL_TIME: + if allowance_type in ProctoredExamStudentAllowance.ADDITIONAL_TIME_GRANTED: err_msg = ( u'allowance_value "{value}" should be a non-negative integer value' ).format(value=value) @@ -631,7 +633,7 @@ def add_bulk_allowances(exam_ids, user_ids, allowance_type, value): for user_id in user_ids: try: add_allowance_for_user(exam_id, user_id, - ProctoredExamStudentAllowance.ADDITIONAL_TIME_GRANTED, + allowance_type, value) successes += 1 data.append({ diff --git a/edx_proctoring/constants.py b/edx_proctoring/constants.py index 3396b2d9c8e..cec9c49d042 100644 --- a/edx_proctoring/constants.py +++ b/edx_proctoring/constants.py @@ -75,5 +75,3 @@ CONTENT_VIEWABLE_PAST_DUE_DATE = getattr(settings, 'PROCTORED_EXAM_VIEWABLE_PAST_DUE', False) TIME_MULTIPLIER = 'time_multiplier' - -ADDITIONAL_TIME = 'additional_time' diff --git a/edx_proctoring/settings/common.py b/edx_proctoring/settings/common.py index b04e79fef77..80841654025 100644 --- a/edx_proctoring/settings/common.py +++ b/edx_proctoring/settings/common.py @@ -16,6 +16,7 @@ def plugin_settings(settings): [ 'proctoring/js/models/proctored_exam_allowance_model.js', 'proctoring/js/models/proctored_exam_attempt_model.js', + 'proctoring/js/models/proctored_exam_bulk_allowance_model.js', 'proctoring/js/models/proctored_exam_model.js', 'proctoring/js/models/learner_onboarding_model.js', 'proctoring/js/collections/proctored_exam_allowance_collection.js', @@ -24,6 +25,7 @@ def plugin_settings(settings): 'proctoring/js/collections/proctored_exam_collection.js', 'proctoring/js/views/Backbone.ModalDialog.js', 'proctoring/js/views/proctored_exam_add_allowance_view.js', + 'proctoring/js/views/proctored_exam_add_bulk_allowance_view.js', 'proctoring/js/views/proctored_exam_allowance_view.js', 'proctoring/js/views/proctored_exam_attempt_view.js', 'proctoring/js/views/proctored_exam_onboarding_view.js', diff --git a/edx_proctoring/static/proctoring/js/models/proctored_exam_bulk_allowance_model.js b/edx_proctoring/static/proctoring/js/models/proctored_exam_bulk_allowance_model.js new file mode 100644 index 00000000000..dffb201ef9c --- /dev/null +++ b/edx_proctoring/static/proctoring/js/models/proctored_exam_bulk_allowance_model.js @@ -0,0 +1,15 @@ +edx = edx || {}; + +(function(Backbone) { + 'use strict'; + + edx.instructor_dashboard = edx.instructor_dashboard || {}; + edx.instructor_dashboard.proctoring = edx.instructor_dashboard.proctoring || {}; + + edx.instructor_dashboard.proctoring.ProctoredExamBulkAllowanceModel = Backbone.Model.extend({ + url: '/api/edx_proctoring/v1/proctored_exam/bulk_allowance' + + }); + this.edx.instructor_dashboard.proctoring.ProctoredExamBulkAllowanceModel = + edx.instructor_dashboard.proctoring.ProctoredExamBulkAllowanceModel; +}).call(this, Backbone); diff --git a/edx_proctoring/static/proctoring/js/views/proctored_exam_add_bulk_allowance_view.js b/edx_proctoring/static/proctoring/js/views/proctored_exam_add_bulk_allowance_view.js new file mode 100644 index 00000000000..cdd6daf52c8 --- /dev/null +++ b/edx_proctoring/static/proctoring/js/views/proctored_exam_add_bulk_allowance_view.js @@ -0,0 +1,295 @@ +edx = edx || {}; + +(function(Backbone, $, _, gettext) { + 'use strict'; + + edx.instructor_dashboard = edx.instructor_dashboard || {}; + edx.instructor_dashboard.proctoring = edx.instructor_dashboard.proctoring || {}; + + edx.instructor_dashboard.proctoring.AddBulkAllowanceView = Backbone.ModalView.extend({ + name: 'AddBulkAllowanceView', + template: null, + template_url: '/static/proctoring/templates/add-new-bulk-allowance.underscore', + initialize: function(options) { + this.all_exams = options.proctored_exams; + this.proctored_exams = []; + this.timed_exams = []; + this.proctored_exam_allowance_view = options.proctored_exam_allowance_view; + this.course_id = options.course_id; + this.allowance_types = options.allowance_types; + this.model = new edx.instructor_dashboard.proctoring.ProctoredExamBulkAllowanceModel(); + _.bindAll(this, 'render'); + this.loadTemplateData(); + // Backbone.Validation.bind( this, {valid:this.hideError, invalid:this.showError}); + }, + events: { + 'submit form': 'addAllowance', + 'change #proctored_exam': 'selectExam', + 'change #timed_exam': 'selectExam', + 'change #allowance_type': 'selectAllowance', + 'change #exam_type': 'selectExamType' + }, + loadTemplateData: function() { + var self = this; + $.ajax({url: self.template_url, dataType: 'html'}) + .done(function(templateData) { + self.sortExamsByExamType(); + self.template = _.template(templateData); + self.render(); + self.showModal(); + self.updateCss(); + }); + }, + updateCss: function() { + var $el = $(this.el); + $el.find('.modal-header').css({ + color: '#1580b0', + 'font-size': '20px', + 'font-weight': '600', + 'line-height': 'normal', + padding: '10px 15px', + 'border-bottom': '1px solid #ccc' + }); + $el.find('form').css({ + padding: '15px' + }); + $el.find('form table.compact td').css({ + 'vertical-align': 'middle', + padding: '4px 8px' + }); + $el.find('form label').css({ + display: 'block', + 'font-size': '14px', + margin: 0, + cursor: 'default' + }); + $el.find('form #minutes_label').css({ + display: 'inline-block' + }); + $el.find('form input[type="text"]').css({ + height: '26px', + padding: '1px 8px 2px', + 'font-size': '14px', + width: '100%' + }); + $el.find('form input[type="submit"]').css({ + 'margin-top': '10px', + float: 'right' + }); + $el.find('.error-message').css({ + color: '#ff0000', + 'line-height': 'normal', + 'font-size': '14px' + }); + $el.find('.error-response').css({ + color: '#ff0000', + 'line-height': 'normal', + 'font-size': '14px', + padding: '0px 10px 5px 7px' + }); + $el.find('form select').css({ + padding: '2px 0px 2px 2px', + 'font-size': '16px', + width: '100%' + }); + $el.find('#selected_exams').css({ + background: '#fff', + display: 'flex', + 'flex-wrap': 'wrap', + 'align-content': 'flex-start', + 'overflow-x': 'scroll' + }); + $el.find('.tag').css({ + 'font-size': '14px', + height: '15px', + 'margin-right': '5px', + padding: '5px 6px', + border: '1px solid #ccc', + 'border-radius': '3px', + background: '#eee', + display: 'flex', + 'align-items': 'center', + color: '#333', + 'box-shadow': '0 0 4px rgba(0, 0, 0, 0.2), inset 0 1px 1px #fff', + cursor: 'default' + }); + $el.find('.close').css({ + 'font-size': '16px', + margin: '5px' + }); + $el.find('.exam_dropdown').css({ + height: '60px' + }); + }, + getCurrentFormValues: function() { + return { + proctored_exam: this.selectedExams, + allowance_type: $('select#allowance_type').val(), + allowance_value: $('#allowance_value').val(), + user_info: $('#user_info').val() + }; + }, + hideError: function(view, attr) { + var $element = view.$form[attr]; + + $element.removeClass('error'); + $element.parent().find('.error-message').empty(); + }, + showError: function(view, attr, errorMessage) { + var $element = view.$form[attr]; + var $errorMessage; + + $element.addClass('error'); + $errorMessage = $element.parent().find('.error-message'); + if ($errorMessage.length === 0) { + $errorMessage = $("
"); + $element.parent().append($errorMessage); + } + + $errorMessage.empty().append(errorMessage); + this.updateCss(); + }, + addAllowance: function(event) { + var $errorResponse, values, formHasErrors, exams; + var self = this; + event.preventDefault(); + $errorResponse = $('.error-response'); + $errorResponse.html(); + values = this.getCurrentFormValues(); + formHasErrors = false; + exams = ''; + + $('.close').each(function() { + exams += $(this).attr('data-item') + ','; + }); + + $.each(values, function(key, value) { + if (value === '') { + formHasErrors = true; + self.showError(self, key, gettext('Required field')); + } else { + self.hideError(self, key); + } + }); + + if (exams === '') { + formHasErrors = true; + self.showError(self, 'proctored_exam', gettext('Required field')); + } else { + self.hideError(self, 'proctored_exam'); + } + + if (!formHasErrors) { + self.model.fetch({ + headers: { + 'X-CSRFToken': self.proctored_exam_allowance_view.getCSRFToken() + }, + type: 'PUT', + data: { + exam_ids: exams, + user_ids: values.user_info, + allowance_type: values.allowance_type, + value: values.allowance_value + }, + success: function() { + // fetch the allowances again. + $errorResponse.html(); + self.proctored_exam_allowance_view.collection.url = + self.proctored_exam_allowance_view.initial_url + self.course_id + '/allowance'; + self.proctored_exam_allowance_view.hydrate(); + self.hideModal(); + }, + error: function(unused, response) { + var data = $.parseJSON(response.responseText); + $errorResponse.html(gettext(data.detail)); + } + }); + } + }, + selectExamAtIndex: function(examID, examName) { + var createdTag = this.createTag(examName, examID); + $('.exam_dropdown:visible').val('default'); + $('.exam_dropdown:visible option[value=' + examID + ']').remove(); + $('#selected_exams').append(createdTag); + this.updateCss(); + }, + selectExam: function() { + this.selectExamAtIndex($('.exam_dropdown:visible').val(), $('.exam_dropdown:visible :selected').text()); + }, + selectAllowance: function() { + this.updateAllowanceLabels($('#allowance_type').val()); + }, + selectExamType: function() { + $('.close').each(function() { + $(this).trigger('click'); + }); + if ($('#proctored_exam').is(':visible')) { + $('#proctored_exam').hide(); + $('#timed_exam').show(); + $('#allowance_type option[value="review_policy_exception"]').remove(); + } else { + $('#proctored_exam').show(); + $('#timed_exam').hide(); + $('#allowance_type').append(new Option(gettext('Review Policy Exception'), 'review_policy_exception')); + } + this.updateAllowanceLabels($('#allowance_type').val()); + }, + updateAllowanceLabels: function(selectedAllowanceType) { + if (selectedAllowanceType === 'additional_time_granted') { + $('#allowance_value_label').text(gettext('Input Additional Minutes as a Positive Number')); + } else if (selectedAllowanceType === 'time_multiplier') { + $('#allowance_value_label').text(gettext('Input Multiplier as a Number Greater Than 1')); + } else { + $('#allowance_value_label').text(gettext('Add Policy Exception')); + } + }, + sortExamsByExamType: function() { + var self = this; + self.all_exams.forEach(function(exam) { + if (exam.is_proctored) { + self.proctored_exams.push(exam); + } else { + self.timed_exams.push(exam); + } + }); + }, + createTag: function(examName, examID) { + var div = document.createElement('div'); + var span = document.createElement('span'); + var closeIcon = document.createElement('span'); + div.setAttribute('class', 'tag'); + span.innerHTML = examName; + closeIcon.innerHTML = 'x'; + closeIcon.setAttribute('class', 'close'); + closeIcon.setAttribute('data-item', examID); + closeIcon.setAttribute('data-name', examName); + closeIcon.onclick = this.deleteTag; + div.appendChild(span); + div.appendChild(closeIcon); + return div; + }, + deleteTag: function() { + var examID = $(this).data('item'); + var examName = $(this).data('name'); + $(this).closest('div').remove(); + $('.exam_dropdown:visible').append(new Option(examName, examID)); + }, + + render: function() { + $(this.el).html(this.template({ + proctored_exams: this.proctored_exams, + timed_exams: this.timed_exams, + allowance_types: this.allowance_types + })); + + this.$form = { + proctored_exam: this.$('select#proctored_exam'), + timed_exam: this.$('select#timed_exam'), + allowance_type: this.$('select#allowance_type'), + allowance_value: this.$('#allowance_value'), + user_info: this.$('#user_info') + }; + return this; + } + }); +}).call(this, Backbone, $, _, gettext); diff --git a/edx_proctoring/static/proctoring/js/views/proctored_exam_allowance_view.js b/edx_proctoring/static/proctoring/js/views/proctored_exam_allowance_view.js index a05510f2b37..fd53dd045c6 100644 --- a/edx_proctoring/static/proctoring/js/views/proctored_exam_allowance_view.js +++ b/edx_proctoring/static/proctoring/js/views/proctored_exam_allowance_view.js @@ -10,7 +10,8 @@ edx = edx || {}; initialize: function() { this.allowance_types = [ ['additional_time_granted', gettext('Additional Time (minutes)')], - ['review_policy_exception', gettext('Review Policy Exception')] + ['review_policy_exception', gettext('Review Policy Exception')], + ['time_multiplier', gettext('Time Multiplier')] ]; this.collection = new edx.instructor_dashboard.proctoring.ProctoredExamAllowanceCollection(); @@ -143,15 +144,29 @@ edx = edx || {}; }, showAddModal: function(event) { var self = this; + var enableBulkAllowance = + self.$el.data('enable-bulk-allowance'); + enableBulkAllowance = enableBulkAllowance && + enableBulkAllowance.toLowerCase() === 'true'; self.proctoredExamCollection.fetch({ success: function() { - // eslint-disable-next-line no-new - new edx.instructor_dashboard.proctoring.AddAllowanceView({ - course_id: self.course_id, - proctored_exams: self.proctoredExamCollection.toJSON(), - proctored_exam_allowance_view: self, - allowance_types: self.allowance_types - }); + if (!enableBulkAllowance) { + // eslint-disable-next-line no-new + new edx.instructor_dashboard.proctoring.AddAllowanceView({ + course_id: self.course_id, + proctored_exams: self.proctoredExamCollection.toJSON(), + proctored_exam_allowance_view: self, + allowance_types: self.allowance_types + }); + } else { + // eslint-disable-next-line no-new + new edx.instructor_dashboard.proctoring.AddBulkAllowanceView({ + course_id: self.course_id, + proctored_exams: self.proctoredExamCollection.toJSON(), + proctored_exam_allowance_view: self, + allowance_types: self.allowance_types + }); + } } }); event.stopPropagation(); diff --git a/edx_proctoring/static/proctoring/spec/proctored_exam_add_bulk_allowance_spec.js b/edx_proctoring/static/proctoring/spec/proctored_exam_add_bulk_allowance_spec.js new file mode 100644 index 00000000000..ab51a804140 --- /dev/null +++ b/edx_proctoring/static/proctoring/spec/proctored_exam_add_bulk_allowance_spec.js @@ -0,0 +1,337 @@ +describe('ProctoredExamAAllowanceView', function() { + 'use strict'; + + var html = ''; + var allowancesHtml = ''; + var errorAddingAllowance = { + detail: 'Cannot find user' + }; + var expectedProctoredAllowanceJson = [ + { + created: '2015-08-10T09:15:45Z', + id: 1, + modified: '2015-08-10T09:15:45Z', + key: 'additional_time_granted', + value: '1', + proctored_exam: { + content_id: 'i4x://edX/DemoX/sequential/9f5e9b018a244ea38e5d157e0019e60c', + course_id: 'edX/DemoX/Demo_Course', + exam_name: 'Test Exam', + external_id: null, + id: 6, + is_active: true, + is_practice_exam: false, + is_proctored: true, + time_limit_mins: 1 + }, + user: { + username: 'testuser1', + email: 'testuser1@test.com' + } + } + ]; + + var expectedTimedAllowanceJson = [ + { + created: '2015-08-10T09:15:45Z', + id: 1, + modified: '2015-08-10T09:15:45Z', + key: 'additional_time_granted', + value: '1', + proctored_exam: { + content_id: 'i4x://edX/DemoX/sequential/9f5e9b018a244ea38e5d157e0019e60c', + course_id: 'edX/DemoX/Demo_Course', + exam_name: 'Test Exam', + external_id: null, + id: 6, + is_active: true, + is_practice_exam: false, + is_proctored: false, + time_limit_mins: 1 + }, + user: { + username: 'testuser1', + email: 'testuser1@test.com' + } + } + ]; + + var proctoredExamJson = [ + { + exam_name: 'Midterm Exam', + is_proctored: true, + is_practice: false, + id: 5 + }, + { + exam_name: 'Final Exam', + is_proctored: false, + is_practice: false, + id: 6 + }, + { + exam_name: 'Test Exam', + is_proctored: true, + is_practice: true, + id: 7 + } + ]; + + var allowanceTypes = [ + ['additional_time_granted', gettext('Additional Time (minutes)')], + ['review_policy_exception', gettext('Review Policy Exception')], + ['time_multiplier', gettext('Time Multiplier')] + ]; + + beforeEach(function() { + // We have converted the edx_proctoring/static/proctoring/templates/add-new-allowance.underscore template + // from http://www.howtocreate.co.uk/tutorials/jsexamples/syntax/prepareInline.html + + // eslint-disable-next-line max-len + html = '
<%- gettext("Add a New Allowance") %>
\n
\n

\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n
\n \n
\n \n
\n \n
\n \n
\n \n \n
\n
\n
\n \n
\n \n
\n \n
\n \n
\n \n
\n'; + + allowancesHtml = '' + + '<%- gettext("Allowances") %>' + + ' +' + + '<%- gettext("Add Allowance") %>' + + ' ' + + '<% var is_allowances = proctored_exam_allowances.length !== 0 %>' + + '<% if (is_allowances) { %>' + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '<% _.each(proctored_exam_allowances, function(proctored_exam_allowance){ %>' + + '' + + '' + + '<% if (proctored_exam_allowance.user){ %>' + + '' + + '' + + '<% }else{ %>' + + '' + + '<% } %>' + + '' + + '' + + '' + + '<% }); %>' + + '
Exam NameUsernameAllowance TypeAllowance ValueActions
' + + '<%- interpolate(gettext(" %(exam_display_name)s "),' + + '{ exam_display_name: proctored_exam_allowance.proctored_exam.exam_name }, true) %>' + + '' + + '<%- interpolate(gettext(" %(username)s "),' + + '{ username: proctored_exam_allowance.user.username }, true) %>' + + '' + + '<%- interpolate(gettext(" %(email)s "),' + + '{ email: proctored_exam_allowance.user.email }, true) %>' + + 'N/AN/A' + + '<%- interpolate(gettext(" %(allowance_name)s "),' + + '{ allowance_name: proctored_exam_allowance.key_display_name }, true) %>' + + '' + + '<%= proctored_exam_allowance.value %>' + + '' + + '[x]' + + '
' + + '<% } %>'; + this.server = sinon.fakeServer.create(); + this.server.autoRespond = true; + + setFixtures('
'); + // load the underscore template response before calling the proctored exam allowance view. + this.server.respondWith('GET', '/static/proctoring/templates/add-new-bulk-allowance.underscore', + [ + 200, + {'Content-Type': 'text/html'}, + html + ] + ); + this.server.respondWith('GET', '/static/proctoring/templates/course_allowances.underscore', + [ + 200, + {'Content-Type': 'text/html'}, + allowancesHtml + ] + ); + }); + + afterEach(function() { + this.server.restore(); + }); + it('should render the proctored exam add allowance view properly', function() { + var addAllowanceView; + this.server.respondWith('GET', '/api/edx_proctoring/v1/proctored_exam/test_course_id/allowance', + [ + 200, + { + 'Content-Type': 'application/json' + }, + JSON.stringify(expectedProctoredAllowanceJson) + ] + ); + + this.proctored_exam_allowance = new edx.instructor_dashboard.proctoring.ProctoredExamAllowanceView(); + addAllowanceView = new edx.instructor_dashboard.proctoring.AddBulkAllowanceView({ + course_id: 'test_course_id', + proctored_exams: proctoredExamJson, + proctored_exam_allowance_view: this.proctored_exam_allowance, + allowance_types: allowanceTypes + }); + this.server.respond(); + this.server.respond(); + this.server.respond(); + + expect(addAllowanceView.$el.find('#proctored_exam').html()).toContain('Midterm Exam'); + expect(addAllowanceView.$el.find('#proctored_exam').html()).toContain('Test Exam'); + $('#proctored_exam').val('5'); + }); + + + it('should render the timed exam add allowance view properly', function() { + var addAllowanceView; + this.server.respondWith('GET', '/api/edx_proctoring/v1/proctored_exam/test_course_id/allowance', + [ + 200, + { + 'Content-Type': 'application/json' + }, + JSON.stringify(expectedTimedAllowanceJson) + ] + ); + + this.proctored_exam_allowance = new edx.instructor_dashboard.proctoring.ProctoredExamAllowanceView(); + addAllowanceView = new edx.instructor_dashboard.proctoring.AddBulkAllowanceView({ + course_id: 'test_course_id', + proctored_exams: proctoredExamJson, + proctored_exam_allowance_view: this.proctored_exam_allowance, + allowance_types: allowanceTypes + }); + this.server.respond(); + this.server.respond(); + this.server.respond(); + + expect(addAllowanceView.$el.find('#timed_exam').html()).toContain('Final Exam'); + $('#timed_exam').val('6'); + }); + + + it('should add the proctored exam allowance', function() { + this.server.respondWith('GET', '/api/edx_proctoring/v1/proctored_exam/test_course_id/allowance', + [ + 200, + { + 'Content-Type': 'application/json' + }, + JSON.stringify([]) + ] + ); + + this.proctored_exam_allowance = new edx.instructor_dashboard.proctoring.ProctoredExamAllowanceView(); + // eslint-disable-next-line no-new + new edx.instructor_dashboard.proctoring.AddBulkAllowanceView({ + course_id: 'test_course_id', + proctored_exams: proctoredExamJson, + proctored_exam_allowance_view: this.proctored_exam_allowance, + allowance_types: allowanceTypes + }); + + this.server.respond(); + this.server.respond(); + this.server.respond(); + + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .not.toContain('testuser1'); + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .not.toContain('testuser1@test.com'); + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .not.toContain('Additional Time (minutes)'); + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .not.toContain('Test Exam'); + + // add the proctored exam allowance + this.server.respondWith('PUT', '/api/edx_proctoring/v1/proctored_exam/bulk_allowance', + [ + 200, + { + 'Content-Type': 'application/json' + }, + JSON.stringify([]) + ] + ); + }); + it('should send error when adding proctored exam allowance', function() { + var addAllowanceView; + this.server.respondWith('GET', '/api/edx_proctoring/v1/proctored_exam/test_course_id/allowance', + [ + 200, + { + 'Content-Type': 'application/json' + }, + JSON.stringify([]) + ] + ); + + this.proctored_exam_allowance = new edx.instructor_dashboard.proctoring.ProctoredExamAllowanceView(); + addAllowanceView = new edx.instructor_dashboard.proctoring.AddBulkAllowanceView({ + course_id: 'test_course_id', + proctored_exams: proctoredExamJson, + proctored_exam_allowance_view: this.proctored_exam_allowance, + allowance_types: allowanceTypes + }); + + this.server.respond(); + this.server.respond(); + this.server.respond(); + + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .not.toContain('testuser1'); + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .not.toContain('testuser1@test.com'); + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .not.toContain('Additional Time (minutes)'); + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .not.toContain('Test Exam'); + + // add the proctored exam allowance + this.server.respondWith('PUT', '/api/edx_proctoring/v1/proctored_exam/bulk_allowance', + [ + 400, + { + 'Content-Type': 'application/json' + }, + JSON.stringify(errorAddingAllowance) + ] + ); + + // again fetch the results after the proctored exam allowance addition + this.server.respondWith('GET', '/api/edx_proctoring/v1/proctored_exam/test_course_id/allowance', + [ + 200, + { + 'Content-Type': 'application/json' + }, + JSON.stringify(expectedProctoredAllowanceJson) + ] + ); + + // select the form values + // empty value returns error + $('#proctored_exam').val('Test Exam'); + $('#allowance_type').val('Additional Time (minutes)'); + $('#allowance_value').val(''); + $('#user_info').val('testuser1'); + + // trigger the add allowance event. + spyOnEvent('form', 'submit'); + $('form').trigger('submit'); + + expect(addAllowanceView.$el.find('.error-message').html()).toContain('Required field'); + }); +}); diff --git a/edx_proctoring/static/proctoring/templates/add-new-bulk-allowance.underscore b/edx_proctoring/static/proctoring/templates/add-new-bulk-allowance.underscore new file mode 100644 index 00000000000..74dbb238c83 --- /dev/null +++ b/edx_proctoring/static/proctoring/templates/add-new-bulk-allowance.underscore @@ -0,0 +1,105 @@ + + +
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ + +
+
+
+ +
+ +
+ +
+ +
+ +
+ diff --git a/edx_proctoring/tests/test_api.py b/edx_proctoring/tests/test_api.py index dc42e3363e3..f4604f05c3e 100644 --- a/edx_proctoring/tests/test_api.py +++ b/edx_proctoring/tests/test_api.py @@ -65,7 +65,7 @@ update_review_policy ) from edx_proctoring.backends.tests.test_backend import TestBackendProvider -from edx_proctoring.constants import ADDITIONAL_TIME, DEFAULT_CONTACT_EMAIL, TIME_MULTIPLIER +from edx_proctoring.constants import DEFAULT_CONTACT_EMAIL, TIME_MULTIPLIER from edx_proctoring.exceptions import ( AllowanceValueNotAllowedException, BackendProviderSentNoAttemptID, @@ -502,7 +502,7 @@ def test_remove_allowance_for_user(self): @ddt.data( ( - ADDITIONAL_TIME, + ProctoredExamStudentAllowance.ADDITIONAL_TIME_GRANTED, '30', '30', '30' @@ -629,7 +629,7 @@ def test_add_same_exam_bulk_allowance(self): @ddt.data( ( - ADDITIONAL_TIME, + ProctoredExamStudentAllowance.ADDITIONAL_TIME_GRANTED[0], '30', '30', '30' @@ -684,7 +684,7 @@ def test_add_bulk_allowance_invalid_user(self, allowance_type, value, exam1_allo @ddt.data( ( - ADDITIONAL_TIME, + ProctoredExamStudentAllowance.ADDITIONAL_TIME_GRANTED[0], '30', '30', '30' @@ -739,19 +739,19 @@ def test_add_bulk_allowance_invalid_exam(self, allowance_type, value, exam1_allo @ddt.data( ( - ADDITIONAL_TIME, + ProctoredExamStudentAllowance.ADDITIONAL_TIME_GRANTED[0], '3.0' ), ( - ADDITIONAL_TIME, + ProctoredExamStudentAllowance.ADDITIONAL_TIME_GRANTED[0], 'invalid' ), ( - ADDITIONAL_TIME, + ProctoredExamStudentAllowance.ADDITIONAL_TIME_GRANTED[0], '-30' ), ( - ADDITIONAL_TIME, + ProctoredExamStudentAllowance.ADDITIONAL_TIME_GRANTED[0], 'd30' ), ( diff --git a/edx_proctoring/tests/test_views.py b/edx_proctoring/tests/test_views.py index 47f6bc0beba..457c7567af5 100644 --- a/edx_proctoring/tests/test_views.py +++ b/edx_proctoring/tests/test_views.py @@ -32,7 +32,7 @@ from edx_proctoring.backends.tests.test_backend import TestBackendProvider from edx_proctoring.backends.tests.test_review_payload import create_test_review_payload from edx_proctoring.backends.tests.test_software_secure import mock_response_content -from edx_proctoring.constants import ADDITIONAL_TIME, TIME_MULTIPLIER +from edx_proctoring.constants import TIME_MULTIPLIER from edx_proctoring.exceptions import ( BackendProviderOnboardingProfilesException, ProctoredExamIllegalStatusTransition, @@ -4910,12 +4910,20 @@ def setUp(self): @ddt.data( ( - ADDITIONAL_TIME, + 'additional_time_granted', '30' ), ( TIME_MULTIPLIER, '1.5' + ), + ( + 'review_policy_exception', + 'notes' + ), + ( + 'review_policy_exception', + 25 ) ) @ddt.unpack @@ -4943,8 +4951,8 @@ def test_add_bulk_time_allowances(self, allowance_type, value): exam_list = [exam1.id, exam2.id] allowance_data = { - 'exam_ids': exam_list, - 'user_ids': user_id_list, + 'exam_ids': ','.join(str(exam) for exam in exam_list), + 'user_ids': ','.join(str(user) for user in user_id_list), 'allowance_type': allowance_type, 'value': value } @@ -4983,9 +4991,9 @@ def test_add_bulk_allowance_non_staff_user(self): # pylint: disable=invalid-nam exam_list = [exam1.id, exam2.id] allowance_data = { - 'exam_ids': exam_list, - 'user_ids': user_id_list, - 'allowance_type': ADDITIONAL_TIME, + 'exam_ids': ','.join(str(exam) for exam in exam_list), + 'user_ids': ','.join(str(user) for user in user_id_list), + 'allowance_type': 'additional_time_granted', 'value': '30' } response = self.client.put( @@ -4999,12 +5007,20 @@ def test_add_bulk_allowance_non_staff_user(self): # pylint: disable=invalid-nam @ddt.data( ( - ADDITIONAL_TIME, + 'additional_time_granted', '30' ), ( TIME_MULTIPLIER, '1.5' + ), + ( + 'review_policy_exception', + 'notes' + ), + ( + 'review_policy_exception', + 25 ) ) @ddt.unpack @@ -5033,8 +5049,9 @@ def test_add_bulk_allowance_invalid_user(self, allowance_type, value): # pylint exam_list = [exam1.id, exam2.id] allowance_data = { - 'exam_ids': exam_list, - 'user_ids': user_id_list, + 'exam_ids': ','.join(str(exam) for exam in exam_list), + # Add additonal whitesapce for invalid users + 'user_ids': ','.join(str(user) for user in user_id_list) + ',, , ,w', 'allowance_type': allowance_type, 'value': value } @@ -5047,12 +5064,20 @@ def test_add_bulk_allowance_invalid_user(self, allowance_type, value): # pylint @ddt.data( ( - ADDITIONAL_TIME, + 'additional_time_granted', '30' ), ( TIME_MULTIPLIER, '1.5' + ), + ( + 'review_policy_exception', + 'notes' + ), + ( + 'review_policy_exception', + 25 ) ) @ddt.unpack @@ -5080,8 +5105,9 @@ def test_add_bulk_allowance_invalid_exam(self, allowance_type, value): # pylint exam_list = [exam1.id, exam2.id, -99] allowance_data = { - 'exam_ids': exam_list, - 'user_ids': user_id_list, + # Test added whitesapce in the exam id input + 'exam_ids': ','.join(str(exam) for exam in exam_list) + ',2 3, 22,', + 'user_ids': ','.join(str(user) for user in user_id_list), 'allowance_type': allowance_type, 'value': value } @@ -5094,7 +5120,7 @@ def test_add_bulk_allowance_invalid_exam(self, allowance_type, value): # pylint @ddt.data( ( - ADDITIONAL_TIME, + 'additional_time_granted', '-30' ), ( @@ -5131,8 +5157,8 @@ def test_add_bulk_allowance_invalid_allowance_value(self, allowance_type, value) exam_list = [exam1.id, exam2.id] allowance_data = { - 'exam_ids': exam_list, - 'user_ids': user_id_list, + 'exam_ids': ','.join(str(exam) for exam in exam_list), + 'user_ids': ','.join(str(user) for user in user_id_list), 'allowance_type': allowance_type, 'value': value } @@ -5152,9 +5178,9 @@ def test_add_bulk_allowance_all_invalid_data(self): # pylint: disable=invalid-n exam_list = [-99, -98] allowance_data = { - 'exam_ids': exam_list, - 'user_ids': user_id_list, - 'allowance_type': ADDITIONAL_TIME, + 'exam_ids': ','.join(str(exam) for exam in exam_list), + 'user_ids': ','.join(str(user) for user in user_id_list), + 'allowance_type': 'additional_time_granted', 'value': '30' } response = self.client.put( @@ -5169,7 +5195,6 @@ def test_add_bulk_allowance_no_users(self): # pylint: disable=invalid-name Test to add bulk allowance with no users """ # Create exams. - user_id_list = [] exam1 = ProctoredExam.objects.create( course_id='a/b/c', content_id='test_content', @@ -5187,9 +5212,9 @@ def test_add_bulk_allowance_no_users(self): # pylint: disable=invalid-name exam_list = [exam1.id, exam2.id] allowance_data = { - 'exam_ids': exam_list, - 'user_ids': user_id_list, - 'allowance_type': ADDITIONAL_TIME, + 'exam_ids': ','.join(str(exam) for exam in exam_list), + 'user_ids': ' ', + 'allowance_type': 'additional_time_granted', 'value': '30' } response = self.client.put( @@ -5206,12 +5231,11 @@ def test_add_bulk_allowance_no_exams(self): # pylint: disable=invalid-name # Create exams. user_list = self.create_batch_users(3) user_id_list = [user.email for user in user_list] - exam_list = [] allowance_data = { - 'exam_ids': exam_list, - 'user_ids': user_id_list, - 'allowance_type': ADDITIONAL_TIME, + 'exam_ids': ' ', + 'user_ids': ','.join(str(user) for user in user_id_list), + 'allowance_type': 'additional_time_granted', 'value': '30' } response = self.client.put( @@ -5253,9 +5277,9 @@ def test_get_grouped_allowances(self): exam_list = [exam1.id] allowance_data = { - 'exam_ids': exam_list, - 'user_ids': user_id_list, - 'allowance_type': ADDITIONAL_TIME, + 'exam_ids': ','.join(str(exam) for exam in exam_list), + 'user_ids': ','.join(str(user) for user in user_id_list), + 'allowance_type': 'additional_time_granted', 'value': '30' } self.client.put( @@ -5315,9 +5339,9 @@ def test_get_grouped_allowances_non_staff(self): exam_list = [exam1.id, exam2.id] allowance_data = { - 'exam_ids': exam_list, - 'user_ids': user_id_list, - 'allowance_type': ADDITIONAL_TIME, + 'exam_ids': ','.join(str(exam) for exam in exam_list), + 'user_ids': ','.join(str(user) for user in user_id_list), + 'allowance_type': 'additional_time_granted', 'value': '30' } self.client.put( @@ -5377,9 +5401,9 @@ def test_get_grouped_allowances_non_global_staff(self): exam_list = [exam1.id, exam2.id] allowance_data = { - 'exam_ids': exam_list, - 'user_ids': user_id_list, - 'allowance_type': ADDITIONAL_TIME, + 'exam_ids': ','.join(str(exam) for exam in exam_list), + 'user_ids': ','.join(str(user) for user in user_id_list), + 'allowance_type': 'additional_time_granted', 'value': '30' } self.client.put( diff --git a/edx_proctoring/views.py b/edx_proctoring/views.py index 0e8e89da2bf..bd1086deafb 100644 --- a/edx_proctoring/views.py +++ b/edx_proctoring/views.py @@ -1544,16 +1544,23 @@ def put(self, request): HTTP PUT handler. Adds or updates Allowances for many exams and students """ try: + exams = request.data.get('exam_ids', '') + users = request.data.get('user_ids', '') + + # We need to remove whitespace from the exam ids as they are ints + filtered_ids = ''.join(exams.split()) + filtered_users = ''.join(users.split()) data, successes, failures = add_bulk_allowances( - exam_ids=request.data.get('exam_ids', None), - user_ids=request.data.get('user_ids', None), + # We only want to pass ints that are not empty + exam_ids=[each_exam for each_exam in filtered_ids.split(',') if each_exam], + user_ids=[each_user.strip() for each_user in filtered_users.split(',') if each_user], allowance_type=request.data.get('allowance_type', None), value=request.data.get('value', None) ) if successes == 0: return Response( status=status.HTTP_400_BAD_REQUEST, - data=data + data={"detail": _("Enter a valid username or email")} ) if failures > 0: return Response( @@ -1566,7 +1573,7 @@ def put(self, request): except AllowanceValueNotAllowedException: return Response( status=status.HTTP_400_BAD_REQUEST, - data={"detail": _("Must be a Staff User to Perform this request.")} + data={"detail": _("Enter a valid positive value number")} ) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index bff737cb4d5..8b58cc938cc 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,31 +1,31 @@ { "name": "@edx/edx-proctoring", - "version": "3.9.1", + "version": "3.17.2", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.14.5" } }, "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } @@ -77,12 +77,6 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -114,9 +108,9 @@ "dev": true }, "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true }, "active-x-obfuscator": { @@ -351,9 +345,9 @@ "dev": true }, "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", + "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", "dev": true }, "array-find-index": { @@ -495,13 +489,10 @@ "dev": true }, "available-typed-arrays": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", - "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", - "dev": true, - "requires": { - "array-filter": "^1.0.0" - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz", + "integrity": "sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA==", + "dev": true }, "aws-sign2": { "version": "0.5.0", @@ -737,16 +728,6 @@ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "dev": true }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, "bl": { "version": "0.9.5", "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz", @@ -1743,9 +1724,9 @@ } }, "config-chain": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz", - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "dev": true, "requires": { "ini": "^1.3.4", @@ -1807,16 +1788,6 @@ } } }, - "contains-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-1.0.0.tgz", - "integrity": "sha1-NFizMhhWA+ju0Y9RjUoQiIo6vJE=", - "dev": true, - "requires": { - "normalize-path": "^2.1.1", - "path-starts-with": "^1.0.0" - } - }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -1941,9 +1912,9 @@ "dev": true }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { "ms": "2.1.2" @@ -2325,9 +2296,9 @@ } }, "engine.io-client": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.8.5.tgz", - "integrity": "sha512-AYTgHyeVUPitsseqjoedjhYJapNVoSPShbZ+tEUX9/73jgZ/Z3sUlJf9oYgdEBBdVhupUpUqSxH0kBCXlQnmZg==", + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.8.6.tgz", + "integrity": "sha512-6+rInQu8xU7c0fIF6RC4SRKuHVWPt8Xq0bZYS4lMrTwmhRineOlEMsU3X0zS5mHIvCgJsmpOKEX7DhihGk7j0g==", "dev": true, "requires": { "component-emitter": "1.2.1", @@ -2340,7 +2311,7 @@ "parseqs": "0.0.5", "parseuri": "0.0.5", "ws": "~1.1.5", - "xmlhttprequest-ssl": "1.5.3", + "xmlhttprequest-ssl": "1.6.3", "yeast": "0.1.2" }, "dependencies": { @@ -2407,9 +2378,9 @@ } }, "es-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", - "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", + "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", "dev": true, "requires": { "call-bind": "^1.0.2", @@ -2420,14 +2391,14 @@ "has-symbols": "^1.0.2", "is-callable": "^1.2.3", "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.2", - "is-string": "^1.0.5", - "object-inspect": "^1.9.0", + "is-regex": "^1.1.3", + "is-string": "^1.0.6", + "object-inspect": "^1.10.3", "object-keys": "^1.1.1", "object.assign": "^4.1.2", "string.prototype.trimend": "^1.0.4", "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.0" + "unbox-primitive": "^1.0.1" } }, "es-get-iterator": { @@ -3267,14 +3238,13 @@ "dev": true }, "eslint-plugin-import": { - "version": "2.23.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.23.2.tgz", - "integrity": "sha512-LmNoRptHBxOP+nb0PIKz1y6OSzCJlB+0g0IGS3XV4KaKk2q4szqQ6s6F1utVf5ZRkxk/QOTjdxe7v4VjS99Bsg==", + "version": "2.23.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.23.4.tgz", + "integrity": "sha512-6/wP8zZRsnQFiR3iaPFgh5ImVRM1WN5NUWfTIRqwOdeiGJlBcSk82o1FEVq8yXmy4lkIzTo7YhHCIxlU/2HyEQ==", "dev": true, "requires": { "array-includes": "^3.1.3", "array.prototype.flat": "^1.2.4", - "contains-path": "^1.0.0", "debug": "^2.6.9", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.4", @@ -3340,9 +3310,9 @@ } }, "eslint-plugin-react": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.23.2.tgz", - "integrity": "sha512-AfjgFQB+nYszudkxRkTFu0UR1zEQig0ArVMPloKhxwlwkzaw/fBiH0QWcBBhZONlXqQC51+nfqFrkn4EzHcGBw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz", + "integrity": "sha512-KJJIx2SYx7PBx3ONe/mEeMz4YE0Lcr7feJTCMyyKb/341NcjuAgim3Acgan89GfPv7nxXK2+0slu0CWXYM4x+Q==", "dev": true, "requires": { "array-includes": "^3.1.3", @@ -3351,12 +3321,12 @@ "has": "^1.0.3", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.0.4", - "object.entries": "^1.1.3", + "object.entries": "^1.1.4", "object.fromentries": "^2.0.4", - "object.values": "^1.1.3", + "object.values": "^1.1.4", "prop-types": "^15.7.2", "resolve": "^2.0.0-next.3", - "string.prototype.matchall": "^4.0.4" + "string.prototype.matchall": "^4.0.5" }, "dependencies": { "doctrine": { @@ -3869,9 +3839,9 @@ "dev": true }, "fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz", + "integrity": "sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag==", "dev": true }, "fd-slicer": { @@ -3907,13 +3877,6 @@ "flat-cache": "^2.0.1" } }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -4180,6 +4143,15 @@ "optional": true, "requires": { "nan": "~0.8.0" + }, + "dependencies": { + "nan": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-0.8.0.tgz", + "integrity": "sha1-AiqPpen+hCCWSsH7PclOF/RJ9f0=", + "dev": true, + "optional": true + } } }, "fstream": { @@ -4903,9 +4875,9 @@ "dev": true }, "uglify-js": { - "version": "3.13.6", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.6.tgz", - "integrity": "sha512-rRprLwl8RVaS+Qvx3Wh5hPfPBn9++G6xkGlUupya0s5aDmNjI7z3lnRLB3u7sN4OmbB0pWgzhM9BEJyiWAwtAA==", + "version": "3.13.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.10.tgz", + "integrity": "sha512-57H3ACYFXeo1IaZ1w02sfA71wI60MGco/IQFjOqK+WtKoprh7Go2/yvd2HPtoJILO2Or84ncLccI4xoHMTSbGg==", "dev": true, "optional": true }, @@ -5467,9 +5439,9 @@ "dev": true }, "is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", + "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", "dev": true, "requires": { "has": "^1.0.3" @@ -5917,12 +5889,12 @@ "dev": true }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", "dev": true, "requires": { - "minimist": "^1.2.0" + "minimist": "^1.2.5" } }, "jsonfile": { @@ -6094,11 +6066,7 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } + "optional": true }, "http-proxy": { "version": "1.18.1", @@ -6144,13 +6112,6 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", - "dev": true, - "optional": true - }, "object-assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", @@ -7002,18 +6963,18 @@ "dev": true }, "mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", "dev": true }, "mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", "dev": true, "requires": { - "mime-db": "1.47.0" + "mime-db": "1.48.0" } }, "mimic-fn": { @@ -7143,11 +7104,10 @@ "dev": true }, "nan": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-0.8.0.tgz", - "integrity": "sha1-AiqPpen+hCCWSsH7PclOF/RJ9f0=", - "dev": true, - "optional": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-1.0.0.tgz", + "integrity": "sha1-riT4hQgY1mL8q1rPfzuVv6oszzg=", + "dev": true }, "nanomatch": { "version": "1.2.13", @@ -7367,9 +7327,9 @@ } }, "object-inspect": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", - "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", "dev": true }, "object-is": { @@ -7422,15 +7382,14 @@ } }, "object.entries": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.3.tgz", - "integrity": "sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.4.tgz", + "integrity": "sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA==", "dev": true, "requires": { - "call-bind": "^1.0.0", + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1", - "has": "^1.0.3" + "es-abstract": "^1.18.2" } }, "object.fromentries": { @@ -7486,15 +7445,14 @@ } }, "object.values": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz", - "integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", + "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "has": "^1.0.3" + "es-abstract": "^1.18.2" } }, "on-finished": { @@ -7809,9 +7767,9 @@ "dev": true }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "path-root": { @@ -7829,15 +7787,6 @@ "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", "dev": true }, - "path-starts-with": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-starts-with/-/path-starts-with-1.0.0.tgz", - "integrity": "sha1-soJDAV6LE43lcmgqxS2kLmRq2E4=", - "dev": true, - "requires": { - "normalize-path": "^2.1.1" - } - }, "path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -8844,14 +8793,6 @@ "array-map": "~0.0.0", "array-reduce": "~0.0.0", "jsonify": "~0.0.0" - }, - "dependencies": { - "array-filter": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", - "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=", - "dev": true - } } }, "side-channel": { @@ -9205,9 +9146,9 @@ } }, "spdx-license-ids": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.8.tgz", - "integrity": "sha512-NDgA96EnaLSvtbM7trJj+t1LUR3pirkDCcz9nOUlPb5DMBGsH7oES6C3hs3j7R9oHEa1EMvReS/BUAIT5Tcr0g==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", + "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", "dev": true }, "split": { @@ -9388,15 +9329,16 @@ } }, "string.prototype.matchall": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.4.tgz", - "integrity": "sha512-pknFIWVachNcyqRfaQSeu/FUfpvJTe4uskUSZ9Wc1RijsPuzbZ8TyYT8WCNnntCjUEqQ3vUHMAfVj2+wLAisPQ==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz", + "integrity": "sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q==", "dev": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "has-symbols": "^1.0.1", + "es-abstract": "^1.18.2", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.2", "internal-slot": "^1.0.3", "regexp.prototype.flags": "^1.3.1", "side-channel": "^1.0.4" @@ -9776,13 +9718,12 @@ "dev": true }, "tsconfig-paths": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", - "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.10.1.tgz", + "integrity": "sha512-rETidPDgCpltxF7MjBZlAFPUHv5aHH2MymyPvh+vEyWAED4Eb/WeMbsnD/JDr4OKPOA1TssDHgIcpTN5Kh0p6Q==", "dev": true, "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^2.2.0", "minimist": "^1.2.0", "strip-bom": "^3.0.0" } @@ -10396,12 +10337,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.1.0.tgz", "integrity": "sha1-0SG7roYNmZKj1Re6lvVliOR8Z4E=", "dev": true - }, - "nan": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-1.0.0.tgz", - "integrity": "sha1-riT4hQgY1mL8q1rPfzuVv6oszzg=", - "dev": true } } }, @@ -10427,9 +10362,9 @@ "dev": true }, "xmlhttprequest-ssl": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", - "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz", + "integrity": "sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q==", "dev": true }, "xtend": { diff --git a/package.json b/package.json index aa8380efaa2..13a43e08aa8 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,7 @@ { "name": "@edx/edx-proctoring", - "//": "Be sure to update the version number in edx_proctoring/__init__.py", "//": "Note that the version format is slightly different than that of the Python version when using prereleases.", - "version": "3.18.0", + "version": "3.19.0", "main": "edx_proctoring/static/index.js", "scripts": { "test": "gulp test"