From 9fe4cb734c28b8ad72ddca0a4482db790791108b Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Mon, 15 Jul 2013 13:57:22 -0400 Subject: [PATCH 1/7] Use Backbone notifications for course section delete. --- .../contentstore/features/common.py | 6 ++ .../contentstore/features/section.feature | 1 + .../contentstore/features/subsection.feature | 1 + .../coffee/spec/views/overview_spec.coffee | 28 +++++++- cms/static/js/base.js | 70 ++++++++++++------- 5 files changed, 81 insertions(+), 25 deletions(-) diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index 7d52124310e7..a734861e66ea 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -239,6 +239,12 @@ def save_button_disabled(step): assert world.css_has_class(button_css, disabled) +@step('I confirm the prompt') +def confirm_the_prompt(step): + prompt_css = 'a.button.action-primary' + world.css_click(prompt_css) + + def type_in_codemirror(index, text): world.css_click(".CodeMirror", index=index) g = world.css_find("div.CodeMirror.CodeMirror-focused > div > textarea") diff --git a/cms/djangoapps/contentstore/features/section.feature b/cms/djangoapps/contentstore/features/section.feature index 84a9bb991d8b..a08b490c6dc1 100644 --- a/cms/djangoapps/contentstore/features/section.feature +++ b/cms/djangoapps/contentstore/features/section.feature @@ -33,4 +33,5 @@ Feature: Create Section And I have added a new section When I will confirm all alerts And I press the "section" delete icon + And I confirm the prompt Then the section does not exist diff --git a/cms/djangoapps/contentstore/features/subsection.feature b/cms/djangoapps/contentstore/features/subsection.feature index a11467e3f92e..9f5793dbe763 100644 --- a/cms/djangoapps/contentstore/features/subsection.feature +++ b/cms/djangoapps/contentstore/features/subsection.feature @@ -38,4 +38,5 @@ Feature: Create Subsection And I see my subsection on the Courseware page When I will confirm all alerts And I press the "subsection" delete icon + And I confirm the prompt Then the subsection does not exist diff --git a/cms/static/coffee/spec/views/overview_spec.coffee b/cms/static/coffee/spec/views/overview_spec.coffee index ba9b5d891862..3a9a2320f700 100644 --- a/cms/static/coffee/spec/views/overview_spec.coffee +++ b/cms/static/coffee/spec/views/overview_spec.coffee @@ -40,17 +40,31 @@ describe "Course Overview", -> """#" + appendSetFixtures """ +
+ +
+ """#" + spyOn(window, 'saveSetSectionScheduleDate').andCallThrough() # Have to do this here, as it normally gets bound in document.ready() $('a.save-button').click(saveSetSectionScheduleDate) + $('a.delete-section-button').click(deleteSection) + @notificationSpy = spyOn(CMS.Views.Notification.Mini.prototype, 'show').andCallThrough() window.analytics = jasmine.createSpyObj('analytics', ['track']) window.course_location_analytics = jasmine.createSpy() - sinon.useFakeXMLHttpRequest() + @xhr = sinon.useFakeXMLHttpRequest() + requests = @requests = [] + @xhr.onCreate = (req) -> requests.push(req) afterEach -> delete window.analytics delete window.course_location_analytics + @xhr.restore() + @notificationSpy.reset() it "should save model when save is clicked", -> $('a.edit-button').click() @@ -61,3 +75,15 @@ describe "Course Overview", -> $('a.edit-button').click() $('a.save-button').click() expect(@notificationSpy).toHaveBeenCalled() + + it "should delete model when delete is clicked", -> + deleteSpy = spyOn(window, '_deleteItem').andCallThrough() + $('a.delete-section-button').click() + $('a.action-primary').click() + expect(deleteSpy).toHaveBeenCalled() + expect(@requests[0].url).toEqual('/delete_item') + + it "should show a confirmation on delete", -> + $('a.delete-section-button').click() + $('a.action-primary').click() + expect(@notificationSpy).toHaveBeenCalled() diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 329624ef4687..43bc8782b122 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -356,39 +356,61 @@ function createNewUnit(e) { function deleteUnit(e) { e.preventDefault(); - _deleteItem($(this).parents('li.leaf')); + _deleteItem($(this).parents('li.leaf'), 'Unit'); } function deleteSubsection(e) { e.preventDefault(); - _deleteItem($(this).parents('li.branch')); + _deleteItem($(this).parents('li.branch'), 'Subsection'); } function deleteSection(e) { e.preventDefault(); - _deleteItem($(this).parents('section.branch')); -} - -function _deleteItem($el) { - if (!confirm(gettext('Are you sure you wish to delete this item. It cannot be reversed!'))) return; - - var id = $el.data('id'); - - analytics.track('Deleted an Item', { - 'course': course_location_analytics, - 'id': id - }); - - - $.post('/delete_item', { - 'id': id, - 'delete_children': true, - 'delete_all_versions': true - }, - - function(data) { - $el.remove(); + _deleteItem($(this).parents('section.branch'), 'Section'); +} + +function _deleteItem($el, type) { + var confirm = new CMS.Views.Prompt.Confirmation({ + title: gettext('Are you sure you wish to delete this ' + type + '?'), + message: gettext('It cannot be reversed!'), + actions: { + primary: { + text: gettext('OK'), + click: function(view) { + view.hide(); + + var id = $el.data('id'); + + analytics.track('Deleted an Item', { + 'course': course_location_analytics, + 'id': id + }); + + var deleting = new CMS.Views.Notification.Mini({ + title: gettext('Deleting') + '…' + }); + deleting.show(); + + $.post('/delete_item', + {'id': id, + 'delete_children': true, + 'delete_all_versions': true}, + function(data) { + $el.remove(); + deleting.hide(); + } + ); + } + }, + secondary: { + text: gettext('Cancel'), + click: function(view) { + view.hide(); + } + } + } }); + confirm.show(); } function markAsLoaded() { From 5029f460ec41de34666e4f36839dc5f90444e484 Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Mon, 15 Jul 2013 11:46:29 -0400 Subject: [PATCH 2/7] Use Backbone notifications for component delete. --- .../contentstore/features/component.feature | 18 ++++++ .../contentstore/features/component.py | 16 ++++++ .../coffee/src/views/module_edit.coffee | 4 ++ cms/static/coffee/src/views/unit.coffee | 56 ++++++++++++------- 4 files changed, 74 insertions(+), 20 deletions(-) diff --git a/cms/djangoapps/contentstore/features/component.feature b/cms/djangoapps/contentstore/features/component.feature index 2291712f2da0..99330ca36578 100644 --- a/cms/djangoapps/contentstore/features/component.feature +++ b/cms/djangoapps/contentstore/features/component.feature @@ -67,3 +67,21 @@ Feature: Component Adding When I will confirm all alerts And I delete all components Then I see no components + + Scenario: I see a prompt on delete + Given I have opened a new course in studio + And I am editing a new unit + And I add the following components: + | Component | + | Discussion | + And I delete a component + Then I see a prompt + + Scenario: I see a notification on save + Given I have opened a new course in studio + And I am editing a new unit + And I add the following components: + | Component | + | Discussion | + And I edit and save a component + Then I see a notification diff --git a/cms/djangoapps/contentstore/features/component.py b/cms/djangoapps/contentstore/features/component.py index 217ad84591fb..3016ec9c5032 100644 --- a/cms/djangoapps/contentstore/features/component.py +++ b/cms/djangoapps/contentstore/features/component.py @@ -41,6 +41,22 @@ def see_no_components(steps): assert world.is_css_not_present('li.component') +@step(u'I delete a component') +def delete_one_component(step): + world.css_click('a.delete-button') + + +@step(u'I edit and save a component') +def edit_and_save_component(step): + world.css_click('.edit-button') + world.css_click('.save-button') + + +@step(u'I see a (.*)$') +def i_see_a_notification(step, notification_type): + assert world.is_css_present('.wrapper-%s' % notification_type) + + def step_selector_list(data_type, path, index=1): selector_list = ['a[data-type="{}"]'.format(data_type)] if index != 1: diff --git a/cms/static/coffee/src/views/module_edit.coffee b/cms/static/coffee/src/views/module_edit.coffee index 62083fa26d6d..c45feecd419e 100644 --- a/cms/static/coffee/src/views/module_edit.coffee +++ b/cms/static/coffee/src/views/module_edit.coffee @@ -84,11 +84,15 @@ class CMS.Views.ModuleEdit extends Backbone.View data.metadata = _.extend(data.metadata || {}, @changedMetadata()) @hideModal() + saving = new CMS.Views.Notification.Mini + title: gettext('Saving') + '…' + saving.show() @model.save(data).done( => # # showToastMessage("Your changes have been saved.", null, 3) @module = null @render() @$el.removeClass('editing') + saving.hide() ).fail( -> showToastMessage(gettext("There was an error saving your changes. Please try again."), null, 3) ) diff --git a/cms/static/coffee/src/views/unit.coffee b/cms/static/coffee/src/views/unit.coffee index 774ef04f6d0a..f38a517c935a 100644 --- a/cms/static/coffee/src/views/unit.coffee +++ b/cms/static/coffee/src/views/unit.coffee @@ -115,27 +115,43 @@ class CMS.Views.UnitEdit extends Backbone.View @model.save() deleteComponent: (event) => - if not confirm 'Are you sure you want to delete this component? This action cannot be undone.' - return - $component = $(event.currentTarget).parents('.component') - $.post('/delete_item', { - id: $component.data('id') - }, => - analytics.track "Deleted a Component", - course: course_location_analytics - unit_id: unit_location_analytics - id: $component.data('id') - - $component.remove() - # b/c we don't vigilantly keep children up to date - # get rid of it before it hurts someone - # sorry for the js, i couldn't figure out the coffee equivalent - `_this.model.save({children: _this.components()}, - {success: function(model) { - model.unset('children'); - }} - );` + msg = new CMS.Views.Prompt.Confirmation( + title: gettext('Are you sure you want to delete this component?'), + message: gettext('This action cannot be undone.'), + actions: + primary: + text: gettext('OK'), + click: (view) => + view.hide() + deleting = new CMS.Views.Notification.Mini + title: gettext('Deleting') + '…', + deleting.show() + $component = $(event.currentTarget).parents('.component') + $.post('/delete_item', { + id: $component.data('id') + }, => + deleting.hide() + analytics.track "Deleted a Component", + course: course_location_analytics + unit_id: unit_location_analytics + id: $component.data('id') + + $component.remove() + # b/c we don't vigilantly keep children up to date + # get rid of it before it hurts someone + # sorry for the js, i couldn't figure out the coffee equivalent + `_this.model.save({children: _this.components()}, + {success: function(model) { + model.unset('children'); + }} + );` + ) + secondary: + text: gettext('Cancel'), + click: (view) -> + view.hide() ) + msg.show() deleteDraft: (event) -> @wait(true) From 75edc657486d6be6a6301f0ceb429877841eda96 Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Mon, 15 Jul 2013 14:54:11 -0400 Subject: [PATCH 3/7] Use Backbone notifications for setting graded status. --- ...togglesection.feature => course-overview.feature} | 12 +++++++++--- ...-overview-togglesection.py => course-overview.py} | 6 ++++++ cms/static/js/views/grader-select-view.js | 11 ++++++++++- 3 files changed, 25 insertions(+), 4 deletions(-) rename cms/djangoapps/contentstore/features/{studio-overview-togglesection.feature => course-overview.feature} (86%) rename cms/djangoapps/contentstore/features/{studio-overview-togglesection.py => course-overview.py} (95%) diff --git a/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature b/cms/djangoapps/contentstore/features/course-overview.feature similarity index 86% rename from cms/djangoapps/contentstore/features/studio-overview-togglesection.feature rename to cms/djangoapps/contentstore/features/course-overview.feature index e746f3629a85..010430bebb4b 100644 --- a/cms/djangoapps/contentstore/features/studio-overview-togglesection.feature +++ b/cms/djangoapps/contentstore/features/course-overview.feature @@ -1,7 +1,7 @@ -Feature: Overview Toggle Section - In order to quickly view the details of a course's section or to scan the inventory of sections +Feature: Course Overview + In order to quickly view the details of a course's section and set release dates and grading As a course author - I want to toggle the visibility of each section's subsection details in the overview listing + I want to use the course overview page Scenario: The default layout for the overview page is to show sections in expanded view Given I have a course with multiple sections @@ -57,3 +57,9 @@ Feature: Overview Toggle Section And I click the "Expand All Sections" link Then I see the "Collapse All Sections" link And all sections are expanded + + Scenario: Notification is shown on grading status changes + Given I have a course with 1 section + When I navigate to the course overview page + And I change an assignment's grading status + Then I see a notification diff --git a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py b/cms/djangoapps/contentstore/features/course-overview.py similarity index 95% rename from cms/djangoapps/contentstore/features/studio-overview-togglesection.py rename to cms/djangoapps/contentstore/features/course-overview.py index 41e39513ea95..10fa6453b254 100644 --- a/cms/djangoapps/contentstore/features/studio-overview-togglesection.py +++ b/cms/djangoapps/contentstore/features/course-overview.py @@ -118,3 +118,9 @@ def all_sections_are_collapsed(step): subsections = world.css_find(subsection_locator) for index in range(len(subsections)): assert_false(world.css_visible(subsection_locator, index=index)) + + +@step(u"I change an assignment's grading status") +def change_grading_status(step): + world.css_find('a.menu-toggle').click() + world.css_find('.menu li').first.click() diff --git a/cms/static/js/views/grader-select-view.js b/cms/static/js/views/grader-select-view.js index b22e763710a8..a16f5fa1b791 100644 --- a/cms/static/js/views/grader-select-view.js +++ b/cms/static/js/views/grader-select-view.js @@ -81,9 +81,18 @@ CMS.Views.OverviewAssignmentGrader = Backbone.View.extend({ this.removeMenu(e); + var saving = new CMS.Views.Notification.Mini({ + title: gettext('Saving') + '…' + }); + saving.show(); + // TODO I'm not happy with this string fetch via the html for what should be an id. I'd rather use the id attr // of the CourseGradingPolicy model or null for Not Graded (NOTE, change template's if check for is-selected accordingly) - this.assignmentGrade.save('graderType', $(e.target).text()); + this.assignmentGrade.save( + 'graderType', + $(e.target).text(), + {success: function () { saving.hide(); }} + ); this.render(); } From fb0fcc310bdc8830d8546ebd2e404db1b2275d63 Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Tue, 16 Jul 2013 13:44:53 -0400 Subject: [PATCH 4/7] Factor out common code between module editing and course overview. --- cms/djangoapps/contentstore/features/common.py | 5 +++++ cms/djangoapps/contentstore/features/component.feature | 4 ++-- cms/djangoapps/contentstore/features/component.py | 5 ----- cms/djangoapps/contentstore/features/course-overview.feature | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index a734861e66ea..d357c8ae96c4 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -245,6 +245,11 @@ def confirm_the_prompt(step): world.css_click(prompt_css) +@step(u'I am shown a (.*)$') +def i_am_shown_a_notification(step, notification_type): + assert world.is_css_present('.wrapper-%s' % notification_type) + + def type_in_codemirror(index, text): world.css_click(".CodeMirror", index=index) g = world.css_find("div.CodeMirror.CodeMirror-focused > div > textarea") diff --git a/cms/djangoapps/contentstore/features/component.feature b/cms/djangoapps/contentstore/features/component.feature index 99330ca36578..a30ce96ae62c 100644 --- a/cms/djangoapps/contentstore/features/component.feature +++ b/cms/djangoapps/contentstore/features/component.feature @@ -75,7 +75,7 @@ Feature: Component Adding | Component | | Discussion | And I delete a component - Then I see a prompt + Then I am shown a prompt Scenario: I see a notification on save Given I have opened a new course in studio @@ -84,4 +84,4 @@ Feature: Component Adding | Component | | Discussion | And I edit and save a component - Then I see a notification + Then I am shown a notification diff --git a/cms/djangoapps/contentstore/features/component.py b/cms/djangoapps/contentstore/features/component.py index 3016ec9c5032..15727dd99231 100644 --- a/cms/djangoapps/contentstore/features/component.py +++ b/cms/djangoapps/contentstore/features/component.py @@ -52,11 +52,6 @@ def edit_and_save_component(step): world.css_click('.save-button') -@step(u'I see a (.*)$') -def i_see_a_notification(step, notification_type): - assert world.is_css_present('.wrapper-%s' % notification_type) - - def step_selector_list(data_type, path, index=1): selector_list = ['a[data-type="{}"]'.format(data_type)] if index != 1: diff --git a/cms/djangoapps/contentstore/features/course-overview.feature b/cms/djangoapps/contentstore/features/course-overview.feature index 010430bebb4b..b3041b9b189e 100644 --- a/cms/djangoapps/contentstore/features/course-overview.feature +++ b/cms/djangoapps/contentstore/features/course-overview.feature @@ -62,4 +62,4 @@ Feature: Course Overview Given I have a course with 1 section When I navigate to the course overview page And I change an assignment's grading status - Then I see a notification + Then I am shown a notification From ec02a4f39fd7cf8b6aa8518291ca69aa25f5d53b Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Wed, 17 Jul 2013 10:16:51 -0400 Subject: [PATCH 5/7] Studio: changed the copy and prompt type for unit + outline deletions --- cms/static/coffee/src/views/unit.coffee | 14 +++++++------- cms/static/js/base.js | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cms/static/coffee/src/views/unit.coffee b/cms/static/coffee/src/views/unit.coffee index f38a517c935a..14b64b7b9e63 100644 --- a/cms/static/coffee/src/views/unit.coffee +++ b/cms/static/coffee/src/views/unit.coffee @@ -67,8 +67,8 @@ class CMS.Views.UnitEdit extends Backbone.View type = $(event.currentTarget).data('type') @$newComponentTypePicker.slideUp(250) @$(".new-component-#{type}").slideDown(250) - $('html, body').animate({ - scrollTop: @$(".new-component-#{type}").offset().top + $('html, body').animate({ + scrollTop: @$(".new-component-#{type}").offset().top }, 500) closeNewComponent: (event) => @@ -115,12 +115,12 @@ class CMS.Views.UnitEdit extends Backbone.View @model.save() deleteComponent: (event) => - msg = new CMS.Views.Prompt.Confirmation( - title: gettext('Are you sure you want to delete this component?'), - message: gettext('This action cannot be undone.'), + msg = new CMS.Views.Prompt.Warning( + title: gettext('Delete this component?'), + message: gettext('Deleting this component is permanent and cannot be undone.'), actions: primary: - text: gettext('OK'), + text: gettext('Yes, delete this component'), click: (view) => view.hide() deleting = new CMS.Views.Notification.Mini @@ -252,7 +252,7 @@ class CMS.Views.UnitEdit.NameEdit extends Backbone.View class CMS.Views.UnitEdit.LocationState extends Backbone.View initialize: => @model.on('change:state', @render) - + render: => @$el.toggleClass("#{@model.previous('state')}-item #{@model.get('state')}-item") diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 43bc8782b122..25c6c151f3d5 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -370,12 +370,12 @@ function deleteSection(e) { } function _deleteItem($el, type) { - var confirm = new CMS.Views.Prompt.Confirmation({ - title: gettext('Are you sure you wish to delete this ' + type + '?'), - message: gettext('It cannot be reversed!'), + var confirm = new CMS.Views.Prompt.Warning({ + title: gettext('Delete this ' + type + '?'), + message: gettext('Deleting this ' + type + ' is permanent and cannot be undone.'), actions: { primary: { - text: gettext('OK'), + text: gettext('Yes, delete this ' + type), click: function(view) { view.hide(); From 925f0a5ee891588323db1a0bea8fef5ebea0c6bb Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Wed, 17 Jul 2013 14:15:50 -0400 Subject: [PATCH 6/7] Remove `@xhr.restore()` to fix tests. --- cms/static/coffee/spec/views/overview_spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/static/coffee/spec/views/overview_spec.coffee b/cms/static/coffee/spec/views/overview_spec.coffee index 3a9a2320f700..0318e4aea4cb 100644 --- a/cms/static/coffee/spec/views/overview_spec.coffee +++ b/cms/static/coffee/spec/views/overview_spec.coffee @@ -63,7 +63,7 @@ describe "Course Overview", -> afterEach -> delete window.analytics delete window.course_location_analytics - @xhr.restore() + # @xhr.restore() @notificationSpy.reset() it "should save model when save is clicked", -> From e3e6fd2002f2873d96467f975fd0a885a41e598b Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Mon, 22 Jul 2013 11:34:18 -0400 Subject: [PATCH 7/7] Remove commented-out code and add test. --- cms/static/coffee/spec/views/overview_spec.coffee | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cms/static/coffee/spec/views/overview_spec.coffee b/cms/static/coffee/spec/views/overview_spec.coffee index 0318e4aea4cb..d900e4bfb1b2 100644 --- a/cms/static/coffee/spec/views/overview_spec.coffee +++ b/cms/static/coffee/spec/views/overview_spec.coffee @@ -63,7 +63,6 @@ describe "Course Overview", -> afterEach -> delete window.analytics delete window.course_location_analytics - # @xhr.restore() @notificationSpy.reset() it "should save model when save is clicked", -> @@ -83,6 +82,12 @@ describe "Course Overview", -> expect(deleteSpy).toHaveBeenCalled() expect(@requests[0].url).toEqual('/delete_item') + it "should not delete model when cancel is clicked", -> + deleteSpy = spyOn(window, '_deleteItem').andCallThrough() + $('a.delete-section-button').click() + $('a.action-secondary').click() + expect(@requests.length).toEqual(0) + it "should show a confirmation on delete", -> $('a.delete-section-button').click() $('a.action-primary').click()