From 164a469e9dd38e2801721e527d2d0e28df856365 Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Mon, 24 Jun 2013 13:07:09 -0400 Subject: [PATCH 1/8] Factor `showNotificationBar` out into ValidatingView. When all settings require an explicit save, this functionality will be shared between each view (with slight changes, e.g. click handlers.) --- cms/static/js/views/settings/advanced_view.js | 42 ++++--------------- cms/static/js/views/validating_view.js | 34 +++++++++++++++ 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/cms/static/js/views/settings/advanced_view.js b/cms/static/js/views/settings/advanced_view.js index 302a918de1..7be45959a6 100644 --- a/cms/static/js/views/settings/advanced_view.js +++ b/cms/static/js/views/settings/advanced_view.js @@ -57,8 +57,14 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ mode: "application/json", lineNumbers: false, lineWrapping: false, onChange: function(instance, changeobj) { // this event's being called even when there's no change :-( - if (instance.getValue() !== oldValue && !self.notificationBarShowing) { - self.showNotificationBar(); + if (instance.getValue() !== oldValue) { + var message = gettext("Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented."); + self.showNotificationBar(message, + _.bind(self.saveView, self), + _.bind(self.revertView, self)); + if(self.saved) { + self.saved.hide(); + } } }, onFocus : function(mirror) { @@ -97,38 +103,6 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({ } }); }, - showNotificationBar: function() { - var self = this; - var message = gettext("Your changes will not take effect until you save your progress. Take care with key and value formatting, as validation is not implemented.") - var confirm = new CMS.Views.Notification.Warning({ - title: gettext("You've Made Some Changes"), - message: message, - actions: { - primary: { - "text": gettext("Save Changes"), - "class": "action-save", - "click": function() { - self.saveView(); - confirm.hide(); - self.notificationBarShowing = false; - } - }, - secondary: [{ - "text": gettext("Cancel"), - "class": "action-cancel", - "click": function() { - self.revertView(); - confirm.hide(); - self.notificationBarShowing = false; - } - }] - }}); - this.notificationBarShowing = true; - confirm.show(); - if(this.saved) { - this.saved.hide(); - } - }, saveView : function() { // TODO one last verification scan: // call validateKey on each to ensure proper format diff --git a/cms/static/js/views/validating_view.js b/cms/static/js/views/validating_view.js index afb355ad4a..b3f78c2a87 100644 --- a/cms/static/js/views/validating_view.js +++ b/cms/static/js/views/validating_view.js @@ -67,5 +67,39 @@ CMS.Views.ValidatingView = Backbone.View.extend({ // put error on the contained inputs return $(ele).find(inputElements); } + }, + + showNotificationBar: function(message, primaryClick, secondaryClick) { + if(this.notificationBarShowing) { + return; + } + var self = this; + this.confirmation = new CMS.Views.Notification.Warning({ + title: gettext("You've made some changes"), + message: message, + actions: { + primary: { + "text": gettext("Save Changes"), + "class": "action-save", + "click": function() { + primaryClick(); + self.confirmation.hide(); + self.notificationBarShowing = false; + } + }, + secondary: [{ + "text": gettext("Cancel"), + "class": "action-cancel", + "click": function() { + if(secondaryClick) { + secondaryClick(); + } + self.confirmation.hide(); + self.notificationBarShowing = false; + } + }] + }}); + this.notificationBarShowing = true; + this.confirmation.show(); } }); From 9094f1890bd7e4353f2ad284f725b2d623b0737c Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Tue, 25 Jun 2013 14:38:57 -0400 Subject: [PATCH 2/8] Change course settings to require explicit save. Rather than asynchronously saving when a setting is updated, we now prompt the user to confirm their changes and only persist the data if they hit the save button. Lettuce tests are updated to expect this behavior and some new ones are added. --- CHANGELOG.rst | 2 + .../contentstore/features/common.py | 6 +++ .../features/course-settings.feature | 11 +++++ .../contentstore/features/course-settings.py | 27 ++++++----- .../contentstore/features/grading.feature | 10 ++++ .../contentstore/features/grading.py | 13 +++++ .../js/models/settings/course_details.js | 6 +-- .../js/views/settings/main_settings_view.js | 47 +++++++++---------- .../views/settings/settings_grading_view.js | 31 ++++++++---- cms/static/js/views/validating_view.js | 34 ++++++++++---- 10 files changed, 128 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 41c171c52c..4d117a9c73 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -48,6 +48,8 @@ moved to be edited as metadata. XModule: Only write out assets files if the contents have changed. +Studio: Course settings are now saved explicitly. + XModule: Don't delete generated xmodule asset files when compiling (for instance, when XModule provides a coffeescript file, don't delete the associated javascript) diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index bdf07fc5ae..a65ad3a08f 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -55,6 +55,12 @@ def i_have_opened_a_new_course(_step): open_new_course() +@step('I (save|cancel) my changes$') +def save_changes(step, action): + button_css = '.action-%s' % action + world.css_click(button_css) + + ####### HELPER FUNCTIONS ############## def open_new_course(): world.clear_courses() diff --git a/cms/djangoapps/contentstore/features/course-settings.feature b/cms/djangoapps/contentstore/features/course-settings.feature index e869bfe47a..41ee99336d 100644 --- a/cms/djangoapps/contentstore/features/course-settings.feature +++ b/cms/djangoapps/contentstore/features/course-settings.feature @@ -5,15 +5,18 @@ Feature: Course Settings Given I have opened a new course in Studio When I select Schedule and Details And I set course dates + And I save my changes Then I see the set dates on refresh Scenario: User can clear previously set course dates (except start date) Given I have set course dates And I clear all the dates except start + And I save my changes Then I see cleared dates on refresh Scenario: User cannot clear the course start date Given I have set course dates + And I save my changes And I clear the course start date Then I receive a warning about course start date And The previously set start date is shown on refresh @@ -21,5 +24,13 @@ Feature: Course Settings Scenario: User can correct the course start date warning Given I have tried to clear the course start And I have entered a new course start date + And I save my changes Then The warning about course start date goes away And My new course start date is shown on refresh + + Scenario: Settings are only persisted when saved + Given I have set course dates + And I save my changes + When I change fields + And I cancel my changes + Then I do not see the new changes persisted on refresh diff --git a/cms/djangoapps/contentstore/features/course-settings.py b/cms/djangoapps/contentstore/features/course-settings.py index bd86fff9b7..bcf260de23 100644 --- a/cms/djangoapps/contentstore/features/course-settings.py +++ b/cms/djangoapps/contentstore/features/course-settings.py @@ -47,8 +47,6 @@ def test_and_i_set_course_dates(step): set_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME) set_date_or_time(ENROLLMENT_END_TIME_CSS, DUMMY_TIME) - pause() - @step('Then I see the set dates on refresh$') def test_then_i_see_the_set_dates_on_refresh(step): @@ -71,8 +69,6 @@ def test_and_i_clear_all_the_dates_except_start(step): set_date_or_time(ENROLLMENT_START_DATE_CSS, '') set_date_or_time(ENROLLMENT_END_DATE_CSS, '') - pause() - @step('Then I see cleared dates on refresh$') def test_then_i_see_cleared_dates_on_refresh(step): @@ -119,7 +115,6 @@ def test_i_have_tried_to_clear_the_course_start(step): @step('I have entered a new course start date$') def test_i_have_entered_a_new_course_start_date(step): set_date_or_time(COURSE_START_DATE_CSS, '12/22/2013') - pause() @step('The warning about course start date goes away$') @@ -137,6 +132,20 @@ def test_my_new_course_start_date_is_shown_on_refresh(step): verify_date_or_time(COURSE_START_TIME_CSS, DUMMY_TIME) +@step('I change fields$') +def test_i_change_fields(step): + set_date_or_time(COURSE_START_DATE_CSS, '7/7/7777') + set_date_or_time(COURSE_END_DATE_CSS, '7/7/7777') + set_date_or_time(ENROLLMENT_START_DATE_CSS, '7/7/7777') + set_date_or_time(ENROLLMENT_END_DATE_CSS, '7/7/7777') + + +@step('I do not see the new changes persisted on refresh$') +def test_changes_not_shown_on_refresh(step): + reload_the_page(step) + step.then('Then I see the set dates on refresh') + + ############### HELPER METHODS #################### def set_date_or_time(css, date_or_time): """ @@ -153,11 +162,3 @@ def verify_date_or_time(css, date_or_time): Verifies date or time field. """ assert_equal(date_or_time, world.css_find(css).first.value) - - -def pause(): - """ - Must sleep briefly to allow last time save to finish, - else refresh of browser will fail. - """ - time.sleep(float(1)) diff --git a/cms/djangoapps/contentstore/features/grading.feature b/cms/djangoapps/contentstore/features/grading.feature index 78634cb964..3736582f39 100644 --- a/cms/djangoapps/contentstore/features/grading.feature +++ b/cms/djangoapps/contentstore/features/grading.feature @@ -32,6 +32,7 @@ Feature: Course Grading And I have populated the course And I am viewing the grading settings When I change assignment type "Homework" to "New Type" + And I save my changes And I go back to the main course page Then I do see the assignment name "New Type" And I do not see the assignment name "Homework" @@ -49,5 +50,14 @@ Feature: Course Grading And I have populated the course And I am viewing the grading settings When I add a new assignment type "New Type" + And I save my changes And I go back to the main course page Then I do see the assignment name "New Type" + + Scenario: Settings are only persisted when saved + Given I have opened a new course in Studio + And I have populated the course + And I am viewing the grading settings + When I change assignment type "Homework" to "New Type" + And I cancel my changes + Then I do not see the changes persisted on refresh diff --git a/cms/djangoapps/contentstore/features/grading.py b/cms/djangoapps/contentstore/features/grading.py index dc41cda30f..ae672c78a4 100644 --- a/cms/djangoapps/contentstore/features/grading.py +++ b/cms/djangoapps/contentstore/features/grading.py @@ -3,6 +3,7 @@ from lettuce import world, step from common import * +from terrain.steps import reload_the_page @step(u'I am viewing the grading settings') @@ -59,6 +60,8 @@ def change_assignment_name(step, old_name, new_name): for count in range(len(old_name)): f._element.send_keys(Keys.END, Keys.BACK_SPACE) f._element.send_keys(new_name) + # Without this, the "you've made changes" notification won't pop up + f._element.send_keys(Keys.ENTER) @step(u'I go back to the main course page') @@ -91,6 +94,8 @@ def add_assignment_type(step, new_name): name_id = '#course-grading-assignment-name' f = world.css_find(name_id)[4] f._element.send_keys(new_name) + # Without this, the "you've made changes" notification won't pop up + f._element.send_keys(Keys.ENTER) @step(u'I have populated the course') @@ -99,6 +104,14 @@ def populate_course(step): step.given('I have added a new subsection') +@step(u'I do not see the changes persisted on refresh$') +def changes_not_persisted(step): + reload_the_page(step) + name_id = '#course-grading-assignment-name' + ele = world.css_find(name_id)[0] + assert(ele.value == 'Homework') + + def get_type_index(name): name_id = '#course-grading-assignment-name' f = world.css_find(name_id) diff --git a/cms/static/js/models/settings/course_details.js b/cms/static/js/models/settings/course_details.js index b71b4e2ab2..993832f830 100644 --- a/cms/static/js/models/settings/course_details.js +++ b/cms/static/js/models/settings/course_details.js @@ -63,13 +63,13 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({ }, _videokey_illegal_chars : /[^a-zA-Z0-9_-]/g, - save_videosource: function(newsource) { + set_videosource: function(newsource) { // newsource either is