diff --git a/common/test/acceptance/pages/lms/staff_view.py b/common/test/acceptance/pages/lms/staff_view.py index 7ebe14434d..749842a190 100644 --- a/common/test/acceptance/pages/lms/staff_view.py +++ b/common/test/acceptance/pages/lms/staff_view.py @@ -66,6 +66,15 @@ class StaffDebugPage(PageObject): self.q(css='input[id^=sd_fu_]').fill(user) self.q(css='section.staff-modal a#staff-debug-sdelete').click() + def rescore(self, user=None): + """ + This clicks on the reset attempts link with an optionally + specified user. + """ + if user: + self.q(css='input[id^=sd_fu_]').first.fill(user) + self.q(css='section.staff-modal a#staff-debug-rescore').click() + @property def idash_msg(self): """ diff --git a/common/test/acceptance/tests/test_staff_view.py b/common/test/acceptance/tests/test_staff_view.py index a202441710..2d39739315 100644 --- a/common/test/acceptance/tests/test_staff_view.py +++ b/common/test/acceptance/tests/test_staff_view.py @@ -16,6 +16,7 @@ class StaffDebugTest(UniqueCourseTest): Tests that verify the staff debug info. """ USERNAME = "STAFF_TESTER" + EMAIL = "johndoe@example.com" def setUp(self): super(StaffDebugTest, self).setUp() @@ -49,7 +50,7 @@ class StaffDebugTest(UniqueCourseTest): # Auto-auth register for the course. # Do this as global staff so that you will see the Staff View - AutoAuthPage(self.browser, username=self.USERNAME, + AutoAuthPage(self.browser, username=self.USERNAME, email=self.EMAIL, course_id=self.course_id, staff=True).visit() def _goto_staff_page(self): @@ -63,15 +64,14 @@ class StaffDebugTest(UniqueCourseTest): def test_reset_attempts_empty(self): """ - Test that we fail properly when there is no student state + Test that we reset even when there is no student state """ staff_debug_page = self._goto_staff_page().open_staff_debug_info() staff_debug_page.reset_attempts() msg = staff_debug_page.idash_msg[0] - self.assertIn((u"Found a single student. Found module. Couldn't " - "reset module state for {0}/").format(self.USERNAME), - msg) + self.assertEqual(u'Successfully reset the attempts ' + 'for user {}'.format(self.USERNAME), msg) def test_delete_state_empty(self): """ @@ -80,8 +80,8 @@ class StaffDebugTest(UniqueCourseTest): staff_debug_page = self._goto_staff_page().open_staff_debug_info() staff_debug_page.delete_state() msg = staff_debug_page.idash_msg[0] - self.assertIn((u"Found a single student. Found module. " - "Deleted student module state for"), msg) + self.assertEqual(u'Successfully deleted student state ' + 'for user {}'.format(self.USERNAME), msg) def test_reset_attempts_state(self): """ @@ -93,10 +93,25 @@ class StaffDebugTest(UniqueCourseTest): staff_debug_page = staff_page.open_staff_debug_info() staff_debug_page.reset_attempts() msg = staff_debug_page.idash_msg[0] - self.assertIn((u"Found a single student. Found module. Module " - "state successfully reset!"), msg) + self.assertEqual(u'Successfully reset the attempts ' + 'for user {}'.format(self.USERNAME), msg) - def test_student_state_state(self): + def test_rescore_state(self): + """ + Rescore the student + """ + staff_page = self._goto_staff_page() + staff_page.answer_problem() + + staff_debug_page = staff_page.open_staff_debug_info() + staff_debug_page.rescore() + msg = staff_debug_page.idash_msg[0] + # Since we aren't running celery stuff, this will fail badly + # for now, but is worth excercising that bad of a response + self.assertEqual(u'Unsuccessfully rescored problem. ' + 'Unknown Error Occurred.', msg) + + def test_student_state_delete(self): """ Successfully delete the student state with an answer """ @@ -106,5 +121,31 @@ class StaffDebugTest(UniqueCourseTest): staff_debug_page = staff_page.open_staff_debug_info() staff_debug_page.delete_state() msg = staff_debug_page.idash_msg[0] - self.assertIn((u"Found a single student. Found module. " - "Deleted student module state for"), msg) + self.assertEqual(u'Successfully deleted student state ' + 'for user {}'.format(self.USERNAME), msg) + + def test_student_by_email(self): + """ + Successfully reset the student attempts using their email address + """ + staff_page = self._goto_staff_page() + staff_page.answer_problem() + + staff_debug_page = staff_page.open_staff_debug_info() + staff_debug_page.reset_attempts(self.EMAIL) + msg = staff_debug_page.idash_msg[0] + self.assertEqual(u'Successfully reset the attempts ' + 'for user {}'.format(self.EMAIL), msg) + + def test_bad_student(self): + """ + Test negative response with invalid user + """ + staff_page = self._goto_staff_page() + staff_page.answer_problem() + + staff_debug_page = staff_page.open_staff_debug_info() + staff_debug_page.delete_state('INVALIDUSER') + msg = staff_debug_page.idash_msg[0] + self.assertEqual(u'Unsuccessfully deleted student state. ' + 'User does not exist.', msg) diff --git a/lms/static/js/spec/staff_debug_actions_spec.js b/lms/static/js/spec/staff_debug_actions_spec.js index 5e8041e0ef..347dd6d584 100644 --- a/lms/static/js/spec/staff_debug_actions_spec.js +++ b/lms/static/js/spec/staff_debug_actions_spec.js @@ -6,7 +6,7 @@ describe('StaffDebugActions', function() { describe('get_url ', function() { it('defines url to courseware ajax entry point', function() { spyOn(StaffDebug, "get_current_url").andReturn("/courses/edX/Open_DemoX/edx_demo_course/courseware/stuff"); - expect(StaffDebug.get_url('instructor')).toBe('/courses/edX/Open_DemoX/edx_demo_course/instructor'); + expect(StaffDebug.get_url('rescore_problem')).toBe('/courses/edX/Open_DemoX/edx_demo_course/instructor_dashboard/api/rescore_problem'); }); }); @@ -26,21 +26,22 @@ describe('StaffDebugActions', function() { $('#' + fixture_id).remove(); }); }); - describe('reset', function() { it('makes an ajax call with the expected parameters', function() { $('body').append(fixture); spyOn($, 'ajax'); - StaffDebug.reset(loc) + StaffDebug.reset(loc); - expect($.ajax.mostRecentCall.args[0]['type']).toEqual('POST'); + expect($.ajax.mostRecentCall.args[0]['type']).toEqual('GET'); expect($.ajax.mostRecentCall.args[0]['data']).toEqual({ - 'action': "Reset student's attempts", - 'problem_for_student': loc, - 'unique_student_identifier': 'userman' + 'problem_to_reset': loc, + 'unique_student_identifier': 'userman', + 'delete_module': false }); - expect($.ajax.mostRecentCall.args[0]['url']).toEqual('/instructor'); + expect($.ajax.mostRecentCall.args[0]['url']).toEqual( + '/instructor_dashboard/api/reset_student_attempts' + ); $('#' + fixture_id).remove(); }); }); @@ -49,17 +50,40 @@ describe('StaffDebugActions', function() { $('body').append(fixture); spyOn($, 'ajax'); - StaffDebug.sdelete(loc) + StaffDebug.sdelete(loc); - expect($.ajax.mostRecentCall.args[0]['type']).toEqual('POST'); + expect($.ajax.mostRecentCall.args[0]['type']).toEqual('GET'); expect($.ajax.mostRecentCall.args[0]['data']).toEqual({ - 'action': "Delete student state for module", - 'problem_for_student': loc, - 'unique_student_identifier': 'userman' + 'problem_to_reset': loc, + 'unique_student_identifier': 'userman', + 'delete_module': true }); - expect($.ajax.mostRecentCall.args[0]['url']).toEqual('/instructor'); + expect($.ajax.mostRecentCall.args[0]['url']).toEqual( + '/instructor_dashboard/api/reset_student_attempts' + ); + $('#' + fixture_id).remove(); }); }); + describe('rescore', function() { + it('makes an ajax call with the expected parameters', function() { + $('body').append(fixture); + + spyOn($, 'ajax'); + StaffDebug.rescore(loc); + + expect($.ajax.mostRecentCall.args[0]['type']).toEqual('GET'); + expect($.ajax.mostRecentCall.args[0]['data']).toEqual({ + 'problem_to_reset': loc, + 'unique_student_identifier': 'userman', + 'delete_module': false + }); + expect($.ajax.mostRecentCall.args[0]['url']).toEqual( + '/instructor_dashboard/api/rescore_problem' + ); + $('#' + fixture_id).remove(); + }); + }); + }); diff --git a/lms/static/js/staff_debug_actions.js b/lms/static/js/staff_debug_actions.js index c749109790..959112a8a2 100644 --- a/lms/static/js/staff_debug_actions.js +++ b/lms/static/js/staff_debug_actions.js @@ -7,7 +7,7 @@ var StaffDebug = (function(){ get_url = function(action){ var pathname = this.get_current_url(); - var url = pathname.substr(0,pathname.indexOf('/courseware')) + '/' + action; + var url = pathname.substr(0,pathname.indexOf('/courseware')) + '/instructor_dashboard/api/' + action; return url; } @@ -19,50 +19,89 @@ var StaffDebug = (function(){ return uname; } - do_idash_action = function(locname, idaction){ + do_idash_action = function(action){ var pdata = { - 'action': idaction, - 'problem_for_student': locname, - 'unique_student_identifier': get_user(locname) + 'problem_to_reset': action.location, + 'unique_student_identifier': get_user(action.location), + 'delete_module': action.delete_module } - $.ajax({ - type: "POST", - url: get_url('instructor'), + type: "GET", + url: get_url(action.method), data: pdata, success: function(data){ - var msg = $("#idash_msg", data); - $("#result_" + locname).html( msg ); - }, - error: function(request, status, error) { var text = _.template( - gettext('Something has gone wrong with this request. \ - The server replied with a status of: {error}'), - {error: error}, + gettext('Successfully {action} for user {user}'), + { + action: action.description, + user: data.student + }, {interpolate: /\{(.+?)\}/g} ) var html = _.template( - '
{text}
', + '{text}
', {text: text}, {interpolate: /\{(.+?)\}/g} ) - $("#result_"+locname).html(html); + $("#result_"+action.location).html(html); }, - dataType: 'html' + error: function(request, status, error) { + var response_json; + try { + response_json = $.parseJSON(request.responseText); + } catch(e) { + response_json = { error: gettext('Unknown Error Occurred.') }; + } + var text = _.template( + gettext('Unsuccessfully {action}. {error}'), + { + error: response_json.error, + action: action.description + }, + {interpolate: /\{(.+?)\}/g} + ) + var html = _.template( + '{text}
', + {text: text}, + {interpolate: /\{(.+?)\}/g} + ) + $("#result_"+action.location).html(html); + }, + dataType: 'json' }); } reset = function(locname){ - do_idash_action(locname, "Reset student's attempts"); + this.do_idash_action({ + location: locname, + method: 'reset_student_attempts', + description: gettext('reset the attempts'), + delete_module: false + }); } sdelete = function(locname){ - do_idash_action(locname, "Delete student state for module"); + this.do_idash_action({ + location: locname, + method: 'reset_student_attempts', + description: gettext('deleted student state'), + delete_module: true + }); + } + + rescore = function(locname){ + this.do_idash_action({ + location: locname, + method: 'rescore_problem', + description: gettext('rescored problem'), + delete_module: false + }); } return { reset: reset, sdelete: sdelete, + rescore: rescore, do_idash_action: do_idash_action, get_current_url: get_current_url, get_url: get_url, @@ -80,4 +119,8 @@ $(document).ready(function() { StaffDebug.sdelete($(this).data('location')); return false; }); + $('#staff-debug-rescore').click(function() { + StaffDebug.rescore($(this).data('location')); + return false; + }); }); diff --git a/lms/static/sass/course/courseware/_courseware.scss b/lms/static/sass/course/courseware/_courseware.scss index 051b98f088..32b5797640 100644 --- a/lms/static/sass/course/courseware/_courseware.scss +++ b/lms/static/sass/course/courseware/_courseware.scss @@ -191,6 +191,15 @@ div.course-wrapper { } } + div.staff_actions { + p.error { + color: $error-red + } + p.success { + color: $success-color; + } + } + div.staff_info { display: none; @include clearfix(); diff --git a/lms/templates/staff_problem_info.html b/lms/templates/staff_problem_info.html index 03032a4976..4bdc157d95 100644 --- a/lms/templates/staff_problem_info.html +++ b/lms/templates/staff_problem_info.html @@ -54,20 +54,25 @@ ${block_content}