From 514e2374972b1e4b06eb7d318cd3a09a142ee718 Mon Sep 17 00:00:00 2001 From: mohtamba Date: Wed, 21 Jul 2021 12:26:19 -0400 Subject: [PATCH 1/7] Working Grouped Allowance Added a feature where students' allowances are grouped by student instead of previously not having any grouping. --- .../js/views/proctored_exam_allowance_view.js | 79 +++++++++++++------ .../course_grouped_allowances.underscore | 64 +++++++++++++++ edx_proctoring/tests/test_views.py | 24 +++--- edx_proctoring/urls.py | 2 +- edx_proctoring/views.py | 30 +++---- 5 files changed, 147 insertions(+), 52 deletions(-) create mode 100644 edx_proctoring/static/proctoring/templates/course_grouped_allowances.underscore 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 fd53dd045c6..73be7d293c4 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 @@ -19,9 +19,17 @@ edx = edx || {}; /* unfortunately we have to make some assumptions about what is being set up in HTML */ this.setElement($('.special-allowance-container')); this.course_id = this.$el.data('course-id'); - + /* we need to check if the bulk allowance waffle flag is enabled */ + this.enableBulkAllowance = + this.$el.data('enable-bulk-allowance'); + this.enableBulkAllowance = this.enableBulkAllowance && + this.enableBulkAllowance.toLowerCase() === 'true'; /* this should be moved to a 'data' attribute in HTML */ - this.template_url = '/static/proctoring/templates/course_allowances.underscore'; + if (this.enableBulkAllowance) { + this.template_url = '/static/proctoring/templates/course_grouped_allowances.underscore'; + } else { + this.template_url = '/static/proctoring/templates/course_allowances.underscore'; + } this.template = null; this.initial_url = this.collection.url; this.allowance_url = this.initial_url + 'allowance'; @@ -32,11 +40,11 @@ edx = edx || {}; this.loadTemplateData(); this.proctoredExamCollection.url = this.proctoredExamCollection.url + this.course_id; - this.collection.url = this.initial_url + this.course_id + '/allowance'; }, events: { 'click #add-allowance': 'showAddModal', - 'click .remove_allowance': 'removeAllowance' + 'click .remove_allowance': 'removeAllowance', + 'click .accordion-trigger': 'toggleAllowanceAccordion' }, getCSRFToken: function() { var cookieValue = null; @@ -75,7 +83,6 @@ edx = edx || {}; }, success: function() { // fetch the allowances again. - self.collection.url = self.initial_url + self.course_id + '/allowance'; self.hydrate(); } } @@ -113,6 +120,11 @@ edx = edx || {}; /* we might - at some point - add a visual element to the */ /* loading, like a spinner */ var self = this; + if (self.enableBulkAllowance) { + self.collection.url = self.initial_url + self.course_id + '/grouped/allowance'; + } else { + self.collection.url = self.initial_url + self.course_id + '/allowance'; + } self.collection.fetch({ success: function() { self.render(); @@ -126,31 +138,32 @@ edx = edx || {}; var self = this; var key, i, html; if (this.template !== null) { - this.collection.each(function(item) { - key = item.get('key'); - for (i = 0; i < self.allowance_types.length; i += 1) { - if (key === self.allowance_types[i][0]) { - item.set('key_display_name', self.allowance_types[i][1]); - break; + if (!this.enableBulkAllowance) { + this.collection.each(function(item) { + key = item.get('key'); + for (i = 0; i < self.allowance_types.length; i += 1) { + if (key === self.allowance_types[i][0]) { + item.set('key_display_name', self.allowance_types[i][1]); + break; + } } - } - if (!item.has('key_display_name')) { - item.set('key_display_name', key); - } - }); - html = this.template({proctored_exam_allowances: this.collection.toJSON()}); + if (!item.has('key_display_name')) { + item.set('key_display_name', key); + } + }); + html = this.template({proctored_exam_allowances: this.collection.toJSON()}); + } else { + html = this.template({proctored_exam_allowances: this.collection.toJSON()[0], + allowance_types: self.allowance_types}); + } this.$el.html(html); } }, showAddModal: function(event) { var self = this; - var enableBulkAllowance = - self.$el.data('enable-bulk-allowance'); - enableBulkAllowance = enableBulkAllowance && - enableBulkAllowance.toLowerCase() === 'true'; self.proctoredExamCollection.fetch({ success: function() { - if (!enableBulkAllowance) { + if (!self.enableBulkAllowance) { // eslint-disable-next-line no-new new edx.instructor_dashboard.proctoring.AddAllowanceView({ course_id: self.course_id, @@ -171,6 +184,28 @@ edx = edx || {}; }); event.stopPropagation(); event.preventDefault(); + }, + toggleAllowanceAccordion: function(event) { + // based on code from openedx/features/course_experience/static/course_experience/js/CourseOutline.js + // but modified to better fit this feature's needs + var accordionRow, isExpanded, $toggleChevron, $contentPanel; + accordionRow = event.currentTarget; + if (accordionRow.classList.contains('accordion-trigger')) { + isExpanded = accordionRow.getAttribute('aria-expanded') === 'true'; + if (!isExpanded) { + $toggleChevron = $(accordionRow).find('.fa-chevron-right'); + $contentPanel = $('#' + accordionRow.innerText.trim()); + $contentPanel.show(); + $toggleChevron.addClass('fa-rotate-90'); + accordionRow.setAttribute('aria-expanded', 'true'); + } else { + $toggleChevron = $(accordionRow).find('.fa-chevron-right'); + $contentPanel = $('#' + accordionRow.innerText.trim()); + $contentPanel.hide(); + $toggleChevron.removeClass('fa-rotate-90'); + accordionRow.setAttribute('aria-expanded', 'false'); + } + } } }); this.edx.instructor_dashboard.proctoring.ProctoredExamAllowanceView = diff --git a/edx_proctoring/static/proctoring/templates/course_grouped_allowances.underscore b/edx_proctoring/static/proctoring/templates/course_grouped_allowances.underscore new file mode 100644 index 00000000000..d0333ba9cb0 --- /dev/null +++ b/edx_proctoring/static/proctoring/templates/course_grouped_allowances.underscore @@ -0,0 +1,64 @@ + <%- gettext("Allowances") %> + + + <%- gettext("Add Allowance") %> + + +<% var is_allowances = proctored_exam_allowances.length !== 0 %> +<% if (is_allowances) { %> + +
+
+ <% _.each(proctored_exam_allowances, function(student){ %> + + + + + + + + + + + <% _.each(student, function(proctored_exam_allowance){ %> + <% var key = proctored_exam_allowance.key; %> + <% for (i = 0; i < allowance_types.length; i += 1) { %> + <% if (key === allowance_types[i][0]) { %> + <% proctored_exam_allowance.key_display_name = allowance_types[i][1]; %> + <% break; %> + <% }} %> + <% if (!proctored_exam_allowance.key_display_name) { %> + <% proctored_exam_allowance.key_display_name = key;} %> + + + + <% }else{ %> + + + <% } %> + + + + + <% }); %> + + + <% }); %> +
+
+<% } %> diff --git a/edx_proctoring/tests/test_views.py b/edx_proctoring/tests/test_views.py index 457c7567af5..56b38bfe29c 100644 --- a/edx_proctoring/tests/test_views.py +++ b/edx_proctoring/tests/test_views.py @@ -5293,23 +5293,23 @@ def test_get_grouped_allowances(self): ) # Create expected dictionary by getting each users allowance seperately - first_user = user_list[0].id + first_user = user_list[0].username first_user_allowance = ProctoredExamStudentAllowance.get_allowance_for_user(exam1.id, first_user, 'additional_time_granted') first_serialized_allowance = ProctoredExamStudentAllowanceSerializer(first_user_allowance).data - second_user = user_list[1].id + second_user = user_list[1].username second_user_allowance = ProctoredExamStudentAllowance.get_allowance_for_user(exam1.id, second_user, 'additional_time_granted') second_serialized_allowance = ProctoredExamStudentAllowanceSerializer(second_user_allowance).data - third_user = user_list[2].id + third_user = user_list[2].username third_user_allowance = ProctoredExamStudentAllowance.get_allowance_for_user(exam1.id, third_user, 'additional_time_granted') third_serialized_allowance = ProctoredExamStudentAllowanceSerializer(third_user_allowance).data - expected_response = { - 'grouped_allowances': {str(first_user): [first_serialized_allowance], - str(second_user): [second_serialized_allowance], - str(third_user): [third_serialized_allowance]} - } + expected_response = {{ + str(first_user): [first_serialized_allowance], + str(second_user): [second_serialized_allowance], + str(third_user): [third_serialized_allowance]} + } response = self.client.get(url) self.assertEqual(response.status_code, 200) response_data = json.loads(response.content.decode('utf-8')) @@ -5372,9 +5372,7 @@ def test_get_grouped_allowances_course_no_allowances(self): response = self.client.get(url) self.assertEqual(response.status_code, 200) response_data = json.loads(response.content.decode('utf-8')) - self.assertEqual(len(response_data), 1) - grouped_allowances = response_data['grouped_allowances'] - self.assertEqual(len(grouped_allowances), 0) + self.assertEqual(len(response_data), 0) def test_get_grouped_allowances_non_global_staff(self): """ @@ -5419,9 +5417,7 @@ def test_get_grouped_allowances_non_global_staff(self): response = self.client.get(url) self.assertEqual(response.status_code, 200) response_data = json.loads(response.content.decode('utf-8')) - self.assertEqual(len(response_data), 1) - grouped_allowances = response_data['grouped_allowances'] - self.assertEqual(len(grouped_allowances), 3) + self.assertEqual(len(response_data), 3) class TestActiveExamsForUserView(LoggedInTestCase): diff --git a/edx_proctoring/urls.py b/edx_proctoring/urls.py index 24e7e7ba09a..175cf1f294a 100644 --- a/edx_proctoring/urls.py +++ b/edx_proctoring/urls.py @@ -91,7 +91,7 @@ name='proctored_exam.bulk_allowance' ), url( - r'edx_proctoring/v1/proctored_exam/allowance/grouped/course_id/{}$'.format(settings.COURSE_ID_PATTERN), + r'edx_proctoring/v1/proctored_exam/{}/grouped/allowance$'.format(settings.COURSE_ID_PATTERN), views.GroupedExamAllowancesByStudent.as_view(), name='proctored_exam.allowance.grouped.course' ), diff --git a/edx_proctoring/views.py b/edx_proctoring/views.py index bd1086deafb..5de3dae9b0b 100644 --- a/edx_proctoring/views.py +++ b/edx_proctoring/views.py @@ -1588,17 +1588,17 @@ class GroupedExamAllowancesByStudent(ProctoredAPIView): HTTP GET: The response will contain a dictionary with the allowances of a course grouped by student. For example: - {'grouped_allowances':{'4': - [{'id': 4, 'created': '2021-06-21T14:47:17.847221Z', - 'modified': '2021-06-21T14:47:17.847221Z', - 'user': {'id': 4, 'username': 'student1', - 'email': 'student1@test.com'}, - 'key': 'additional_time_granted', 'value': '30', - 'proctored_exam': {'id': 2, 'course_id': 'a/b/c', 'content_id': 'test_content2', - 'external_id': None, 'exam_name': 'Test Exam2', 'time_limit_mins': 90, - 'is_proctored': False, 'is_practice_exam': False, - 'is_active': True, 'due_date': None, - 'hide_after_due': False, 'backend': None}}} + {{'student1': + [{'id': 4, 'created': '2021-06-21T14:47:17.847221Z', + 'modified': '2021-06-21T14:47:17.847221Z', + 'user': {'id': 4, 'username': 'student1', + 'email': 'student1@test.com'}, + 'key': 'additional_time_granted', 'value': '30', + 'proctored_exam': {'id': 2, 'course_id': 'a/b/c', 'content_id': 'test_content2', + 'external_id': None, 'exam_name': 'Test Exam2', 'time_limit_mins': 90, + 'is_proctored': False, 'is_practice_exam': False, + 'is_active': True, 'due_date': None, + 'hide_after_due': False, 'backend': None}}} **Exceptions** HTTP GET: @@ -1616,13 +1616,13 @@ def get(self, request, course_id): grouped_allowances = {} - # Process allowances so they are grouped by user id + # Process allowances so they are grouped by username for allowance in all_allowances: serialied_allowance = ProctoredExamStudentAllowanceSerializer(allowance).data - user_id = serialied_allowance['user']['id'] - grouped_allowances.setdefault(user_id, []).append(serialied_allowance) + username = serialied_allowance['user']['username'] + grouped_allowances.setdefault(username, []).append(serialied_allowance) - response_data = {'grouped_allowances': grouped_allowances} + response_data = grouped_allowances return Response(response_data) From e2fbdce94852955b8e90d7e69a2c34b70a6cc00c Mon Sep 17 00:00:00 2001 From: mohtamba Date: Wed, 21 Jul 2021 14:39:09 -0400 Subject: [PATCH 2/7] Update test_views.py --- edx_proctoring/tests/test_views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/edx_proctoring/tests/test_views.py b/edx_proctoring/tests/test_views.py index 56b38bfe29c..4db27a25ec0 100644 --- a/edx_proctoring/tests/test_views.py +++ b/edx_proctoring/tests/test_views.py @@ -5293,22 +5293,22 @@ def test_get_grouped_allowances(self): ) # Create expected dictionary by getting each users allowance seperately - first_user = user_list[0].username + first_user = user_list[0].id first_user_allowance = ProctoredExamStudentAllowance.get_allowance_for_user(exam1.id, first_user, 'additional_time_granted') first_serialized_allowance = ProctoredExamStudentAllowanceSerializer(first_user_allowance).data - second_user = user_list[1].username + second_user = user_list[1].id second_user_allowance = ProctoredExamStudentAllowance.get_allowance_for_user(exam1.id, second_user, 'additional_time_granted') second_serialized_allowance = ProctoredExamStudentAllowanceSerializer(second_user_allowance).data - third_user = user_list[2].username + third_user = user_list[2].id third_user_allowance = ProctoredExamStudentAllowance.get_allowance_for_user(exam1.id, third_user, 'additional_time_granted') third_serialized_allowance = ProctoredExamStudentAllowanceSerializer(third_user_allowance).data expected_response = {{ - str(first_user): [first_serialized_allowance], - str(second_user): [second_serialized_allowance], - str(third_user): [third_serialized_allowance]} + str(user_list[0].username): [first_serialized_allowance], + str(user_list[1].username): [second_serialized_allowance], + str(user_list[2].username): [third_serialized_allowance]} } response = self.client.get(url) self.assertEqual(response.status_code, 200) From 085340a86c22a4af11a28b47834966bbe15501be Mon Sep 17 00:00:00 2001 From: mohtamba Date: Wed, 21 Jul 2021 14:48:10 -0400 Subject: [PATCH 3/7] Update test_views.py --- edx_proctoring/tests/test_views.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/edx_proctoring/tests/test_views.py b/edx_proctoring/tests/test_views.py index 4db27a25ec0..1ae73e515e4 100644 --- a/edx_proctoring/tests/test_views.py +++ b/edx_proctoring/tests/test_views.py @@ -5305,11 +5305,10 @@ def test_get_grouped_allowances(self): third_user_allowance = ProctoredExamStudentAllowance.get_allowance_for_user(exam1.id, third_user, 'additional_time_granted') third_serialized_allowance = ProctoredExamStudentAllowanceSerializer(third_user_allowance).data - expected_response = {{ - str(user_list[0].username): [first_serialized_allowance], - str(user_list[1].username): [second_serialized_allowance], - str(user_list[2].username): [third_serialized_allowance]} - } + expected_response = { + user_list[0].username: [first_serialized_allowance], + user_list[1].username: [second_serialized_allowance], + user_list[2].username: [third_serialized_allowance]} response = self.client.get(url) self.assertEqual(response.status_code, 200) response_data = json.loads(response.content.decode('utf-8')) From 8e190fdd123a92b29121de32108a5a0bf3de2a94 Mon Sep 17 00:00:00 2001 From: mohtamba Date: Fri, 23 Jul 2021 08:28:33 -0400 Subject: [PATCH 4/7] Create proctored_exam_bulk_allowance_spec.js create test cases for the course grouped allowances --- .../proctored_exam_bulk_allowance_spec.js | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 edx_proctoring/static/proctoring/spec/proctored_exam_bulk_allowance_spec.js diff --git a/edx_proctoring/static/proctoring/spec/proctored_exam_bulk_allowance_spec.js b/edx_proctoring/static/proctoring/spec/proctored_exam_bulk_allowance_spec.js new file mode 100644 index 00000000000..6786d7cc8a8 --- /dev/null +++ b/edx_proctoring/static/proctoring/spec/proctored_exam_bulk_allowance_spec.js @@ -0,0 +1,139 @@ +describe('ProctoredExamAllowanceView', function() { + 'use strict'; + + var html = ''; + var expectedProctoredAllowanceJson = [ + { + created: '2015-08-10T09:15:45Z', + id: 1, + modified: '2015-08-10T09:15:45Z', + key: 'Additional time (minutes)', + 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: 17, + is_active: true, + is_practice_exam: false, + is_proctored: true, + time_limit_mins: 1 + }, + user: { + username: 'testuser1', + email: 'testuser1@test.com' + } + } + ]; + + beforeEach(function() { + // eslint-disable-next-line max-len + html = ' <%- gettext("Allowances") %>\n \n + <%- gettext("Add Allowance") %>\n \n\n<% var is_allowances = proctored_exam_allowances.length !== 0 %>\n<% if (is_allowances) { %>\n\n
\n
\n <% _.each(proctored_exam_allowances, function(student){ %>\n \n \n \n \n \n \n \n \n \n \n <% _.each(student, function(proctored_exam_allowance){ %>\n <% var key = proctored_exam_allowance.key; %>\n <% for (i = 0; i < allowance_types.length; i += 1) { %>\n <% if (key === allowance_types[i][0]) { %>\n <% proctored_exam_allowance.key_display_name = allowance_types[i][1]; %>\n <% break; %>\n <% }} %>\n <% if (!proctored_exam_allowance.key_display_name) { %>\n <% proctored_exam_allowance.key_display_name = key;} %>\n \n \n \n <% }else{ %>\n \n \n <% } %>\n \n \n \n \n <% }); %>\n \n \n <% }); %>\n
\n
\n<% } %>\n'; + 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/course_grouped_allowances.underscore', + [ + 200, + {'Content-Type': 'text/html'}, + html + ] + ); + }); + + afterEach(function() { + this.server.restore(); + }); + it('should render the proctored exam allowance view properly', function() { + this.server.respondWith('GET', '/api/edx_proctoring/v1/proctored_exam/test_course_id/grouped/allowance', + [ + 200, + { + 'Content-Type': 'application/json' + }, + JSON.stringify(expectedProctoredAllowanceJson) + ] + ); + + this.proctored_exam_allowance = new edx.instructor_dashboard.proctoring.ProctoredExamAllowanceView(); + this.server.respond(); + this.server.respond(); + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .toContain('testuser1'); + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .toContain('testuser1@test.com'); + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .toContain('Additional time (minutes)'); + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .toContain('Test Exam'); + }); + // + it('should remove the proctored exam allowance', function() { + this.server.respondWith('GET', '/api/edx_proctoring/v1/proctored_exam/test_course_id/grouped/allowance', + [ + 200, + { + 'Content-Type': 'application/json' + }, + JSON.stringify(expectedProctoredAllowanceJson) + ] + ); + + this.proctored_exam_allowance = new edx.instructor_dashboard.proctoring.ProctoredExamAllowanceView(); + + this.server.respond(); + this.server.respond(); + + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .toContain('testuser1'); + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .toContain('testuser1@test.com'); + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .toContain('Additional time (minutes)'); + expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) + .toContain('Test Exam'); + + // delete the proctored exam allowance one by one + this.server.respondWith('DELETE', '/api/edx_proctoring/v1/proctored_exam/grouped/allowance', + [ + 200, + { + 'Content-Type': 'application/json' + }, + JSON.stringify([]) + ] + ); + + // again fetch the results after the proctored exam allowance deletion + this.server.respondWith('GET', '/api/edx_proctoring/v1/proctored_exam/test_course_id/grouped/allowance', + [ + 200, + { + 'Content-Type': 'application/json' + }, + JSON.stringify([]) + ] + ); + + // trigger the remove allowance event. + spyOnEvent('.remove_allowance', 'click'); + $('.remove_allowance').trigger('click'); + + // process the deleted allowance requests. + 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'); + }); +}); From 2a98ee4f51572fc22699773cb63307dcbfe2b6ea Mon Sep 17 00:00:00 2001 From: mohtamba Date: Fri, 23 Jul 2021 09:57:26 -0400 Subject: [PATCH 5/7] Fix JS-lint issue --- .../proctoring/spec/proctored_exam_bulk_allowance_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edx_proctoring/static/proctoring/spec/proctored_exam_bulk_allowance_spec.js b/edx_proctoring/static/proctoring/spec/proctored_exam_bulk_allowance_spec.js index 6786d7cc8a8..4dd1d7ec5b9 100644 --- a/edx_proctoring/static/proctoring/spec/proctored_exam_bulk_allowance_spec.js +++ b/edx_proctoring/static/proctoring/spec/proctored_exam_bulk_allowance_spec.js @@ -29,7 +29,7 @@ describe('ProctoredExamAllowanceView', function() { beforeEach(function() { // eslint-disable-next-line max-len - html = ' <%- gettext("Allowances") %>\n \n + <%- gettext("Add Allowance") %>\n \n\n<% var is_allowances = proctored_exam_allowances.length !== 0 %>\n<% if (is_allowances) { %>\n\n
\n
\n <% _.each(proctored_exam_allowances, function(student){ %>\n \n \n \n \n \n \n \n \n \n \n <% _.each(student, function(proctored_exam_allowance){ %>\n <% var key = proctored_exam_allowance.key; %>\n <% for (i = 0; i < allowance_types.length; i += 1) { %>\n <% if (key === allowance_types[i][0]) { %>\n <% proctored_exam_allowance.key_display_name = allowance_types[i][1]; %>\n <% break; %>\n <% }} %>\n <% if (!proctored_exam_allowance.key_display_name) { %>\n <% proctored_exam_allowance.key_display_name = key;} %>\n \n \n \n <% }else{ %>\n \n \n <% } %>\n \n \n \n \n <% }); %>\n \n \n <% }); %>\n
\n
\n<% } %>\n'; + html = ' <%- gettext("Allowances") %>\n \n + <%- gettext("Add Allowance") %>\n \n\n<% var is_allowances = proctored_exam_allowances.length !== 0 %>\n<% if (is_allowances) { %>\n\n
\n
\n <% _.each(proctored_exam_allowances, function(student){ %>\n \n \n \n \n \n \n \n \n \n \n <% _.each(student, function(proctored_exam_allowance){ %>\n <% var key = proctored_exam_allowance.key; %>\n <% for (i = 0; i < allowance_types.length; i += 1) { %>\n <% if (key === allowance_types[i][0]) { %>\n <% proctored_exam_allowance.key_display_name = allowance_types[i][1]; %>\n <% break; %>\n <% }} %>\n <% if (!proctored_exam_allowance.key_display_name) { %>\n <% proctored_exam_allowance.key_display_name = key;} %>\n \n \n \n <% }else{ %>\n \n \n <% } %>\n \n \n \n \n <% }); %>\n \n \n <% }); %>\n
\n
\n<% } %>\n'; this.server = sinon.fakeServer.create(); this.server.autoRespond = true; setFixtures('
Date: Fri, 23 Jul 2021 11:12:31 -0400 Subject: [PATCH 6/7] Fix grouped allowance tests --- .../proctored_exam_bulk_allowance_spec.js | 45 ++----------------- 1 file changed, 3 insertions(+), 42 deletions(-) diff --git a/edx_proctoring/static/proctoring/spec/proctored_exam_bulk_allowance_spec.js b/edx_proctoring/static/proctoring/spec/proctored_exam_bulk_allowance_spec.js index 4dd1d7ec5b9..4d2f217cdcb 100644 --- a/edx_proctoring/static/proctoring/spec/proctored_exam_bulk_allowance_spec.js +++ b/edx_proctoring/static/proctoring/spec/proctored_exam_bulk_allowance_spec.js @@ -2,8 +2,8 @@ describe('ProctoredExamAllowanceView', function() { 'use strict'; var html = ''; - var expectedProctoredAllowanceJson = [ - { + var expectedProctoredAllowanceJson = [{ + student: [{ created: '2015-08-10T09:15:45Z', id: 1, modified: '2015-08-10T09:15:45Z', @@ -24,7 +24,7 @@ describe('ProctoredExamAllowanceView', function() { username: 'testuser1', email: 'testuser1@test.com' } - } + }]} ]; beforeEach(function() { @@ -96,44 +96,5 @@ describe('ProctoredExamAllowanceView', function() { .toContain('Additional time (minutes)'); expect(this.proctored_exam_allowance.$el.find('tr.allowance-items').html()) .toContain('Test Exam'); - - // delete the proctored exam allowance one by one - this.server.respondWith('DELETE', '/api/edx_proctoring/v1/proctored_exam/grouped/allowance', - [ - 200, - { - 'Content-Type': 'application/json' - }, - JSON.stringify([]) - ] - ); - - // again fetch the results after the proctored exam allowance deletion - this.server.respondWith('GET', '/api/edx_proctoring/v1/proctored_exam/test_course_id/grouped/allowance', - [ - 200, - { - 'Content-Type': 'application/json' - }, - JSON.stringify([]) - ] - ); - - // trigger the remove allowance event. - spyOnEvent('.remove_allowance', 'click'); - $('.remove_allowance').trigger('click'); - - // process the deleted allowance requests. - 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'); }); }); From 4bed05db1e9bd55e9cb403c7ea3a02702258f346 Mon Sep 17 00:00:00 2001 From: mohtamba Date: Fri, 23 Jul 2021 13:25:37 -0400 Subject: [PATCH 7/7] Updated version number and changelog --- CHANGELOG.rst | 5 +++++ edx_proctoring/__init__.py | 2 +- package.json | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9f8ba7cdf64..79c67dd3f11 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,11 @@ Change Log Unreleased ~~~~~~~~~~ +[3.21.0] - 2021-07-23 +~~~~~~~~~~~~~~~~~~~~~ +* Added feature behind the bulk allowance waffle flag that groups allowances by users. +* Updated the UI so allowances are under dropdown for each user + [3.20.6] - 2021-07-22 ~~~~~~~~~~~~~~~~~~~~~ * Removed use of name field in proctored exam attempt admin. diff --git a/edx_proctoring/__init__.py b/edx_proctoring/__init__.py index e75ce94b16b..133ec1ee68a 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.20.6' +__version__ = '3.21.0' default_app_config = 'edx_proctoring.apps.EdxProctoringConfig' # pylint: disable=invalid-name diff --git a/package.json b/package.json index e0ab1a6cd76..48027fe82db 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@edx/edx-proctoring", "//": "Note that the version format is slightly different than that of the Python version when using prereleases.", - "version": "3.20.6", + "version": "3.21.0", "main": "edx_proctoring/static/index.js", "scripts": { "test": "gulp test"