EDUCATOR-141: Moved the Discussions Mgt to its own tab and updated tests

This commit is contained in:
Albert St. Aubin
2017-05-02 14:01:14 -04:00
committed by cahrens
parent 8951ac8c61
commit c23c0b991b
37 changed files with 1523 additions and 1180 deletions

View File

@@ -47,6 +47,15 @@ class InstructorDashboardPage(CoursePage):
cohort_management_section.wait_for_page()
return cohort_management_section
def select_discussion_management(self):
"""
Selects the Discussion tab and returns the DiscussionmanagementSection
"""
self.q(css='[data-section="discussions_management"').first.click()
discussion_management_section = DiscussionManagementSection(self.browser)
discussion_management_section.wait_for_page()
return discussion_management_section
def select_data_download(self):
"""
Selects the data download tab and returns a DataDownloadPage.
@@ -666,61 +675,31 @@ class CohortManagementSection(PageObject):
self.q(css=self._bounded_selector('.cohorts-state')).first.click()
self.wait_for_ajax()
def toggles_showing_of_discussion_topics(self):
def cohort_management_controls_visible(self):
"""
Shows the discussion topics.
Return the visibility status of cohort management controls(cohort selector section etc).
"""
self.q(css=self._bounded_selector(".toggle-cohort-management-discussions")).first.click()
self.wait_for_element_visibility("#cohort-discussions-management", "Waiting for discussions to appear")
return (self.q(css=self._bounded_selector('.cohort-management-nav')).visible and
self.q(css=self._bounded_selector('.wrapper-cohort-supplemental')).visible)
def discussion_topics_visible(self):
"""
Returns the visibility status of cohort discussion controls.
"""
EmptyPromise(
lambda: self.q(css=self._bounded_selector('.cohort-discussions-nav')).results != 0,
"Waiting for discussion section to show"
).fulfill()
return (self.q(css=self._bounded_selector('.cohort-course-wide-discussions-nav')).visible and
self.q(css=self._bounded_selector('.cohort-inline-discussions-nav')).visible)
class DiscussionManagementSection(PageObject):
def select_discussion_topic(self, key):
"""
Selects discussion topic checkbox by clicking on it.
"""
self.q(css=self._bounded_selector(".check-discussion-subcategory-%s" % key)).first.click()
url = None
def select_always_inline_discussion(self):
"""
Selects the always_cohort_inline_discussions radio button.
"""
self.q(css=self._bounded_selector(".check-all-inline-discussions")).first.click()
discussion_form_selectors = {
'course-wide': '.cohort-course-wide-discussions-form',
'inline': '.cohort-inline-discussions-form'
}
def always_inline_discussion_selected(self):
"""
Returns true if always_cohort_inline_discussions radio button is selected.
"""
return len(self.q(css=self._bounded_selector(".check-all-inline-discussions:checked"))) > 0
def is_browser_on_page(self):
return self.q(css=self.discussion_form_selectors['course-wide']).present
def cohort_some_inline_discussion_selected(self):
def _bounded_selector(self, selector):
"""
Returns true if some_cohort_inline_discussions radio button is selected.
Return `selector`, but limited to the divided discussion management context.
"""
return len(self.q(css=self._bounded_selector(".check-cohort-inline-discussions:checked"))) > 0
def select_cohort_some_inline_discussion(self):
"""
Selects the cohort_some_inline_discussions radio button.
"""
self.q(css=self._bounded_selector(".check-cohort-inline-discussions")).first.click()
def inline_discussion_topics_disabled(self):
"""
Returns the status of inline discussion topics, enabled or disabled.
"""
inline_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-inline'))
return all(topic.get_attribute('disabled') == 'true' for topic in inline_topics)
return '.discussions-management {}'.format(selector)
def is_save_button_disabled(self, key):
"""
@@ -730,18 +709,36 @@ class CohortManagementSection(PageObject):
disabled = self.q(css=self._bounded_selector(save_button_css)).attrs('disabled')
return disabled[0] == 'true'
def is_category_selected(self):
def discussion_topics_visible(self):
"""
Returns the status for category checkboxes.
Returns the visibility status of divide discussion controls.
"""
return self.q(css=self._bounded_selector('.check-discussion-category:checked')).is_present()
return (self.q(css=self._bounded_selector('.course-wide-discussions-nav')).visible and
self.q(css=self._bounded_selector('.inline-discussions-nav')).visible)
def get_cohorted_topics_count(self, key):
def divided_discussion_heading_is_visible(self, key):
"""
Returns the count for cohorted topics.
Returns the text of discussion topic headings if it exists, otherwise return False.
"""
cohorted_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-%s:checked' % key))
return len(cohorted_topics.results)
form_heading_css = '%s %s' % (self.discussion_form_selectors[key], '.subsection-title')
discussion_heading = self.q(css=self._bounded_selector(form_heading_css))
if len(discussion_heading) == 0:
return False
return discussion_heading.first.text[0]
def select_always_inline_discussion(self):
"""
Selects the always_divide_inline_discussions radio button.
"""
self.q(css=self._bounded_selector(".check-all-inline-discussions")).first.click()
def inline_discussion_topics_disabled(self):
"""
Returns the status of inline discussion topics, enabled or disabled.
"""
inline_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-inline'))
return all(topic.get_attribute('disabled') == 'true' for topic in inline_topics)
def save_discussion_topics(self, key):
"""
@@ -750,7 +747,38 @@ class CohortManagementSection(PageObject):
save_button_css = '%s %s' % (self.discussion_form_selectors[key], '.action-save')
self.q(css=self._bounded_selector(save_button_css)).first.click()
def get_cohort_discussions_message(self, key, msg_type="confirmation"):
def always_inline_discussion_selected(self):
"""
Returns true if always_divide_inline_discussions radio button is selected.
"""
return len(self.q(css=self._bounded_selector(".check-all-inline-discussions:checked"))) > 0
def divide_some_inline_discussion_selected(self):
"""
Returns true if divide_some_inline_discussions radio button is selected.
"""
return len(self.q(css=self._bounded_selector(".check-cohort-inline-discussions:checked"))) > 0
def select_divide_some_inline_discussion(self):
"""
Selects the divide_some_inline_discussions radio button.
"""
self.q(css=self._bounded_selector(".check-cohort-inline-discussions")).first.click()
def get_divided_topics_count(self, key):
"""
Returns the count for divided topics.
"""
divided_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-%s:checked' % key))
return len(divided_topics.results)
def select_discussion_topic(self, key):
"""
Selects discussion topic checkbox by clicking on it.
"""
self.q(css=self._bounded_selector(".check-discussion-subcategory-%s" % key)).first.click()
def get_divide_discussions_message(self, key, msg_type="confirmation"):
"""
Returns the message related to modifying discussion topics.
"""
@@ -767,23 +795,11 @@ class CohortManagementSection(PageObject):
return ''
return message_title.first.text[0]
def cohort_discussion_heading_is_visible(self, key):
def is_category_selected(self):
"""
Returns the visibility of discussion topic headings.
Returns the status for category checkboxes.
"""
form_heading_css = '%s %s' % (self.discussion_form_selectors[key], '.subsection-title')
discussion_heading = self.q(css=self._bounded_selector(form_heading_css))
if len(discussion_heading) == 0:
return False
return discussion_heading.first.text[0]
def cohort_management_controls_visible(self):
"""
Return the visibility status of cohort management controls(cohort selector section etc).
"""
return (self.q(css=self._bounded_selector('.cohort-management-nav')).visible and
self.q(css=self._bounded_selector('.wrapper-cohort-supplemental')).visible)
return self.q(css=self._bounded_selector('.check-discussion-category:checked')).is_present()
class MembershipPageAutoEnrollSection(PageObject):

View File

@@ -76,11 +76,16 @@ class CohortTestMixin(object):
def enable_cohorting(self, course_fixture):
"""
enables cohorting for the current course fixture.
enables cohorts and always_divide_inline_discussions for the current course fixture.
"""
url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/settings' # pylint: disable=protected-access
data = json.dumps({'always_cohort_inline_discussions': True})
discussions_url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/discussions/settings' # pylint: disable=protected-access
data = json.dumps({'is_cohorted': True})
discussions_data = json.dumps({'always_divide_inline_discussions': True})
response = course_fixture.session.patch(url, data=data, headers=course_fixture.headers)
course_fixture.session.patch(discussions_url, data=discussions_data, headers=course_fixture.headers)
def disable_cohorting(self, course_fixture):
"""

View File

@@ -693,267 +693,6 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin
self.cohort_management_page.a11y_audit.check_for_accessibility_errors()
@attr(shard=6)
class CohortDiscussionTopicsTest(UniqueCourseTest, CohortTestMixin):
"""
Tests for cohorting the inline and course-wide discussion topics.
"""
def setUp(self):
"""
Set up a discussion topics
"""
super(CohortDiscussionTopicsTest, self).setUp()
self.discussion_id = "test_discussion_{}".format(uuid.uuid4().hex)
self.course_fixture = CourseFixture(**self.course_info).add_children(
XBlockFixtureDesc("chapter", "Test Section").add_children(
XBlockFixtureDesc("sequential", "Test Subsection").add_children(
XBlockFixtureDesc("vertical", "Test Unit").add_children(
XBlockFixtureDesc(
"discussion",
"Test Discussion",
metadata={"discussion_id": self.discussion_id}
)
)
)
)
).install()
# create course with single cohort and two content groups (user_partition of type "cohort")
self.cohort_name = "OnlyCohort"
self.setup_cohort_config(self.course_fixture)
self.cohort_id = self.add_manual_cohort(self.course_fixture, self.cohort_name)
# login as an instructor
self.instructor_name = "instructor_user"
self.instructor_id = AutoAuthPage(
self.browser, username=self.instructor_name, email="instructor_user@example.com",
course_id=self.course_id, staff=True
).visit().get_user_id()
# go to the membership page on the instructor dashboard
self.instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)
self.instructor_dashboard_page.visit()
self.cohort_management_page = self.instructor_dashboard_page.select_cohort_management()
self.cohort_management_page.wait_for_page()
self.course_wide_key = 'course-wide'
self.inline_key = 'inline'
def cohort_discussion_topics_are_visible(self):
"""
Assert that discussion topics are visible with appropriate content.
"""
self.cohort_management_page.toggles_showing_of_discussion_topics()
self.assertTrue(self.cohort_management_page.discussion_topics_visible())
self.assertEqual(
"Course-Wide Discussion Topics",
self.cohort_management_page.cohort_discussion_heading_is_visible(self.course_wide_key)
)
self.assertTrue(self.cohort_management_page.is_save_button_disabled(self.course_wide_key))
self.assertEqual(
"Content-Specific Discussion Topics",
self.cohort_management_page.cohort_discussion_heading_is_visible(self.inline_key)
)
self.assertTrue(self.cohort_management_page.is_save_button_disabled(self.inline_key))
def save_and_verify_discussion_topics(self, key):
"""
Saves the discussion topics and the verify the changes.
"""
# click on the inline save button.
self.cohort_management_page.save_discussion_topics(key)
# verifies that changes saved successfully.
confirmation_message = self.cohort_management_page.get_cohort_discussions_message(key=key)
self.assertEqual("Your changes have been saved.", confirmation_message)
# save button disabled again.
self.assertTrue(self.cohort_management_page.is_save_button_disabled(key))
def reload_page(self):
"""
Refresh the page.
"""
self.browser.refresh()
self.cohort_management_page.wait_for_page()
self.instructor_dashboard_page.select_cohort_management()
self.cohort_management_page.wait_for_page()
self.cohort_discussion_topics_are_visible()
def verify_discussion_topics_after_reload(self, key, cohorted_topics):
"""
Verifies the changed topics.
"""
self.reload_page()
self.assertEqual(self.cohort_management_page.get_cohorted_topics_count(key), cohorted_topics)
def test_cohort_course_wide_discussion_topic(self):
"""
Scenario: cohort a course-wide discussion topic.
Given I have a course with a cohort defined,
And a course-wide discussion with disabled Save button.
When I click on the course-wide discussion topic
Then I see the enabled save button
When I click on save button
Then I see success message
When I reload the page
Then I see the discussion topic selected
"""
self.cohort_discussion_topics_are_visible()
cohorted_topics_before = self.cohort_management_page.get_cohorted_topics_count(self.course_wide_key)
self.cohort_management_page.select_discussion_topic(self.course_wide_key)
self.assertFalse(self.cohort_management_page.is_save_button_disabled(self.course_wide_key))
self.save_and_verify_discussion_topics(key=self.course_wide_key)
cohorted_topics_after = self.cohort_management_page.get_cohorted_topics_count(self.course_wide_key)
self.assertNotEqual(cohorted_topics_before, cohorted_topics_after)
self.verify_discussion_topics_after_reload(self.course_wide_key, cohorted_topics_after)
def test_always_cohort_inline_topic_enabled(self):
"""
Scenario: Select the always_cohort_inline_topics radio button
Given I have a course with a cohort defined,
And an inline discussion topic with disabled Save button.
When I click on always_cohort_inline_topics
Then I see enabled save button
And I see disabled inline discussion topics
When I save the change
And I reload the page
Then I see the always_cohort_inline_topics option enabled
"""
self.cohort_discussion_topics_are_visible()
# enable always inline discussion topics and save the change
self.cohort_management_page.select_always_inline_discussion()
self.assertFalse(self.cohort_management_page.is_save_button_disabled(self.inline_key))
self.assertTrue(self.cohort_management_page.inline_discussion_topics_disabled())
self.cohort_management_page.save_discussion_topics(key=self.inline_key)
self.reload_page()
self.assertTrue(self.cohort_management_page.always_inline_discussion_selected())
def test_cohort_some_inline_topics_enabled(self):
"""
Scenario: Select the cohort_some_inline_topics radio button
Given I have a course with a cohort defined and always_cohort_inline_topics set to True
And an inline discussion topic with disabled Save button.
When I click on cohort_some_inline_topics
Then I see enabled save button
And I see enabled inline discussion topics
When I save the change
And I reload the page
Then I see the cohort_some_inline_topics option enabled
"""
self.cohort_discussion_topics_are_visible()
# By default always inline discussion topics is False. Enable it (and reload the page).
self.assertFalse(self.cohort_management_page.always_inline_discussion_selected())
self.cohort_management_page.select_always_inline_discussion()
self.cohort_management_page.save_discussion_topics(key=self.inline_key)
self.reload_page()
self.assertFalse(self.cohort_management_page.cohort_some_inline_discussion_selected())
# enable some inline discussion topic radio button.
self.cohort_management_page.select_cohort_some_inline_discussion()
# I see that save button is enabled
self.assertFalse(self.cohort_management_page.is_save_button_disabled(self.inline_key))
# I see that inline discussion topics are enabled
self.assertFalse(self.cohort_management_page.inline_discussion_topics_disabled())
self.cohort_management_page.save_discussion_topics(key=self.inline_key)
self.reload_page()
self.assertTrue(self.cohort_management_page.cohort_some_inline_discussion_selected())
def test_cohort_inline_discussion_topic(self):
"""
Scenario: cohort inline discussion topic.
Given I have a course with a cohort defined,
And a inline discussion topic with disabled Save button
And When I click on inline discussion topic
And I see enabled save button
And When i click save button
Then I see success message
When I reload the page
Then I see the discussion topic selected
"""
self.cohort_discussion_topics_are_visible()
cohorted_topics_before = self.cohort_management_page.get_cohorted_topics_count(self.inline_key)
# check the discussion topic.
self.cohort_management_page.select_discussion_topic(self.inline_key)
# Save button enabled.
self.assertFalse(self.cohort_management_page.is_save_button_disabled(self.inline_key))
# verifies that changes saved successfully.
self.save_and_verify_discussion_topics(key=self.inline_key)
cohorted_topics_after = self.cohort_management_page.get_cohorted_topics_count(self.inline_key)
self.assertNotEqual(cohorted_topics_before, cohorted_topics_after)
self.verify_discussion_topics_after_reload(self.inline_key, cohorted_topics_after)
def test_verify_that_selecting_the_final_child_selects_category(self):
"""
Scenario: Category should be selected on selecting final child.
Given I have a course with a cohort defined,
And a inline discussion with disabled Save button.
When I click on child topics
Then I see enabled saved button
Then I see parent category to be checked.
"""
self.cohort_discussion_topics_are_visible()
# category should not be selected.
self.assertFalse(self.cohort_management_page.is_category_selected())
# check the discussion topic.
self.cohort_management_page.select_discussion_topic(self.inline_key)
# verify that category is selected.
self.assertTrue(self.cohort_management_page.is_category_selected())
def test_verify_that_deselecting_the_final_child_deselects_category(self):
"""
Scenario: Category should be deselected on deselecting final child.
Given I have a course with a cohort defined,
And a inline discussion with disabled Save button.
When I click on final child topics
Then I see enabled saved button
Then I see parent category to be deselected.
"""
self.cohort_discussion_topics_are_visible()
# category should not be selected.
self.assertFalse(self.cohort_management_page.is_category_selected())
# check the discussion topic.
self.cohort_management_page.select_discussion_topic(self.inline_key)
# verify that category is selected.
self.assertTrue(self.cohort_management_page.is_category_selected())
# un-check the discussion topic.
self.cohort_management_page.select_discussion_topic(self.inline_key)
# category should not be selected.
self.assertFalse(self.cohort_management_page.is_category_selected())
@attr(shard=6)
class CohortContentGroupAssociationTest(UniqueCourseTest, CohortTestMixin):
"""

View File

@@ -0,0 +1,273 @@
# -*- coding: utf-8 -*-
"""
End-to-end tests related to the divided discussion management on the LMS Instructor Dashboard
"""
from nose.plugins.attrib import attr
from common.test.acceptance.tests.discussion.helpers import CohortTestMixin
from common.test.acceptance.tests.helpers import UniqueCourseTest
from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc
from common.test.acceptance.pages.lms.auto_auth import AutoAuthPage
from common.test.acceptance.pages.lms.instructor_dashboard import InstructorDashboardPage
import uuid
@attr(shard=6)
class DividedDiscussionTopicsTest(UniqueCourseTest, CohortTestMixin):
"""
Tests for dividing the inline and course-wide discussion topics.
"""
def setUp(self):
"""
Set up a discussion topic
"""
super(DividedDiscussionTopicsTest, self).setUp()
self.discussion_id = "test_discussion_{}".format(uuid.uuid4().hex)
self.course_fixture = CourseFixture(**self.course_info).add_children(
XBlockFixtureDesc("chapter", "Test Section").add_children(
XBlockFixtureDesc("sequential", "Test Subsection").add_children(
XBlockFixtureDesc("vertical", "Test Unit").add_children(
XBlockFixtureDesc(
"discussion",
"Test Discussion",
metadata={"discussion_id": self.discussion_id}
)
)
)
)
).install()
# create course with single cohort and two content groups (user_partition of type "cohort")
self.cohort_name = "OnlyCohort"
self.setup_cohort_config(self.course_fixture)
self.cohort_id = self.add_manual_cohort(self.course_fixture, self.cohort_name)
# login as an instructor
self.instructor_name = "instructor_user"
self.instructor_id = AutoAuthPage(
self.browser, username=self.instructor_name, email="instructor_user@example.com",
course_id=self.course_id, staff=True
).visit().get_user_id()
# go to the membership page on the instructor dashboard
self.instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)
self.instructor_dashboard_page.visit()
self.discussion_management_page = self.instructor_dashboard_page.select_discussion_management()
self.discussion_management_page.wait_for_page()
self.course_wide_key = 'course-wide'
self.inline_key = 'inline'
def divided_discussion_topics_are_visible(self):
"""
Assert that discussion topics are visible with appropriate content.
"""
self.assertTrue(self.discussion_management_page.discussion_topics_visible())
self.assertEqual(
"Course-Wide Discussion Topics",
self.discussion_management_page.divided_discussion_heading_is_visible(self.course_wide_key)
)
self.assertTrue(self.discussion_management_page.is_save_button_disabled(self.course_wide_key))
self.assertEqual(
"Content-Specific Discussion Topics",
self.discussion_management_page.divided_discussion_heading_is_visible(self.inline_key)
)
self.assertTrue(self.discussion_management_page.is_save_button_disabled(self.inline_key))
def save_and_verify_discussion_topics(self, key):
"""
Saves the discussion topics and the verify the changes.
"""
# click on the inline save button.
self.discussion_management_page.save_discussion_topics(key)
# verifies that changes saved successfully.
confirmation_message = self.discussion_management_page.get_divide_discussions_message(key=key)
self.assertEqual("Your changes have been saved.", confirmation_message)
# save button disabled again.
self.assertTrue(self.discussion_management_page.is_save_button_disabled(key))
def reload_page(self):
"""
Refresh the page.
"""
self.browser.refresh()
self.discussion_management_page.wait_for_page()
self.instructor_dashboard_page.select_discussion_management()
self.discussion_management_page.wait_for_page()
self.divided_discussion_topics_are_visible()
def verify_discussion_topics_after_reload(self, key, divided_topics):
"""
Verifies the changed topics.
"""
self.reload_page()
self.assertEqual(self.discussion_management_page.get_divided_topics_count(key), divided_topics)
def test_divide_course_wide_discussion_topic(self):
"""
Scenario: divide a course-wide discussion topic.
Given I have a course with a divide defined,
And a course-wide discussion with disabled Save button.
When I click on the course-wide discussion topic
Then I see the enabled save button
When I click on save button
Then I see success message
When I reload the page
Then I see the discussion topic selected
"""
self.divided_discussion_topics_are_visible()
divided_topics_before = self.discussion_management_page.get_divided_topics_count(self.course_wide_key)
self.discussion_management_page.select_discussion_topic(self.course_wide_key)
self.assertFalse(self.discussion_management_page.is_save_button_disabled(self.course_wide_key))
self.save_and_verify_discussion_topics(key=self.course_wide_key)
divided_topics_after = self.discussion_management_page.get_divided_topics_count(self.course_wide_key)
self.assertNotEqual(divided_topics_before, divided_topics_after)
self.verify_discussion_topics_after_reload(self.course_wide_key, divided_topics_after)
def test_always_divide_inline_topic_enabled(self):
"""
Scenario: Select the always_divide_inline_topics radio button
Given I have a course with a cohort defined,
And an inline discussion topic with disabled Save button.
When I click on always_divide_inline_topics
Then I see enabled save button
And I see disabled inline discussion topics
When I save the change
And I reload the page
Then I see the always_divide_inline_topics option enabled
"""
self.divided_discussion_topics_are_visible()
# enable always inline discussion topics and save the change
self.discussion_management_page.select_always_inline_discussion()
self.assertFalse(self.discussion_management_page.is_save_button_disabled(self.inline_key))
self.assertTrue(self.discussion_management_page.inline_discussion_topics_disabled())
self.discussion_management_page.save_discussion_topics(key=self.inline_key)
self.reload_page()
self.assertTrue(self.discussion_management_page.always_inline_discussion_selected())
def test_divide_some_inline_topics_enabled(self):
"""
Scenario: Select the divide_some_inline_topics radio button
Given I have a course with a divide defined and always_divide_inline_topics set to True
And an inline discussion topic with disabled Save button.
When I click on divide_some_inline_topics
Then I see enabled save button
And I see enabled inline discussion topics
When I save the change
And I reload the page
Then I see the divide_some_inline_topics option enabled
"""
self.divided_discussion_topics_are_visible()
# By default always inline discussion topics is False. Enable it (and reload the page).
self.assertFalse(self.discussion_management_page.always_inline_discussion_selected())
self.discussion_management_page.select_always_inline_discussion()
self.discussion_management_page.save_discussion_topics(key=self.inline_key)
self.reload_page()
self.assertFalse(self.discussion_management_page.divide_some_inline_discussion_selected())
# enable some inline discussion topic radio button.
self.discussion_management_page.select_divide_some_inline_discussion()
# I see that save button is enabled
self.assertFalse(self.discussion_management_page.is_save_button_disabled(self.inline_key))
# I see that inline discussion topics are enabled
self.assertFalse(self.discussion_management_page.inline_discussion_topics_disabled())
self.discussion_management_page.save_discussion_topics(key=self.inline_key)
self.reload_page()
self.assertTrue(self.discussion_management_page.divide_some_inline_discussion_selected())
def test_divide_inline_discussion_topic(self):
"""
Scenario: divide inline discussion topic.
Given I have a course with a divide defined,
And a inline discussion topic with disabled Save button
And When I click on inline discussion topic
And I see enabled save button
And When i click save button
Then I see success message
When I reload the page
Then I see the discussion topic selected
"""
self.divided_discussion_topics_are_visible()
divided_topics_before = self.discussion_management_page.get_divided_topics_count(self.inline_key)
# check the discussion topic.
self.discussion_management_page.select_discussion_topic(self.inline_key)
# Save button enabled.
self.assertFalse(self.discussion_management_page.is_save_button_disabled(self.inline_key))
# verifies that changes saved successfully.
self.save_and_verify_discussion_topics(key=self.inline_key)
divided_topics_after = self.discussion_management_page.get_divided_topics_count(self.inline_key)
self.assertNotEqual(divided_topics_before, divided_topics_after)
self.verify_discussion_topics_after_reload(self.inline_key, divided_topics_after)
def test_verify_that_selecting_the_final_child_selects_category(self):
"""
Scenario: Category should be selected on selecting final child.
Given I have a course with a cohort defined,
And a inline discussion with disabled Save button.
When I click on child topics
Then I see enabled saved button
Then I see parent category to be checked.
"""
self.divided_discussion_topics_are_visible()
# category should not be selected.
self.assertFalse(self.discussion_management_page.is_category_selected())
# check the discussion topic.
self.discussion_management_page.select_discussion_topic(self.inline_key)
# verify that category is selected.
self.assertTrue(self.discussion_management_page.is_category_selected())
def test_verify_that_deselecting_the_final_child_deselects_category(self):
"""
Scenario: Category should be deselected on deselecting final child.
Given I have a course with a cohort defined,
And a inline discussion with disabled Save button.
When I click on final child topics
Then I see enabled saved button
Then I see parent category to be deselected.
"""
self.divided_discussion_topics_are_visible()
# category should not be selected.
self.assertFalse(self.discussion_management_page.is_category_selected())
# check the discussion topic.
self.discussion_management_page.select_discussion_topic(self.inline_key)
# verify that category is selected.
self.assertTrue(self.discussion_management_page.is_category_selected())
# un-check the discussion topic.
self.discussion_management_page.select_discussion_topic(self.inline_key)
# category should not be selected.
self.assertFalse(self.discussion_management_page.is_category_selected())

View File

@@ -26,7 +26,7 @@ from courseware.tests.factories import InstructorFactory
from courseware.tabs import get_course_tab_list
from openedx.core.djangoapps.course_groups import cohorts
from openedx.core.djangoapps.course_groups.cohorts import set_course_cohorted
from openedx.core.djangoapps.course_groups.tests.helpers import config_course_cohorts, topic_name_to_id, CohortFactory
from openedx.core.djangoapps.course_groups.tests.helpers import config_course_cohorts, config_course_discussions, topic_name_to_id, CohortFactory
from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentFactory
from openedx.core.djangoapps.content.course_structures.models import CourseStructure
from openedx.core.djangoapps.util.testing import ContentGroupTestCase
@@ -478,7 +478,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
}
)
def test_inline_with_always_cohort_inline_discussion_flag(self):
def test_inline_with_always_divide_inline_discussion_flag(self):
self.create_discussion("Chapter", "Discussion")
set_discussion_division_settings(self.course.id, enable_cohorts=True, always_divide_inline_discussions=True)
@@ -502,7 +502,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
}
)
def test_inline_without_always_cohort_inline_discussion_flag(self):
def test_inline_without_always_divide_inline_discussion_flag(self):
self.create_discussion("Chapter", "Discussion")
set_discussion_division_settings(self.course.id, enable_cohorts=True)
@@ -1301,15 +1301,16 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
)
# not cohorted
config_course_cohorts(course, is_cohorted=False, discussion_topics=["General", "Feedback"])
config_course_cohorts(course, is_cohorted=False)
config_course_discussions(course, discussion_topics=["General", "Feedback"])
self.assertFalse(
utils.is_commentable_divided(course.id, to_id("General")),
"Course isn't cohorted"
)
# cohorted, but top level topics aren't
config_course_cohorts(course, is_cohorted=True, discussion_topics=["General", "Feedback"])
config_course_cohorts(course, is_cohorted=True)
config_course_discussions(course, discussion_topics=["General", "Feedback"])
self.assertTrue(cohorts.is_course_cohorted(course.id))
self.assertFalse(
@@ -1320,10 +1321,9 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
# cohorted, including "Feedback" top-level topics aren't
config_course_cohorts(
course,
is_cohorted=True,
discussion_topics=["General", "Feedback"],
divided_discussions=["Feedback"]
is_cohorted=True
)
config_course_discussions(course, discussion_topics=["General", "Feedback"], divided_discussions=["Feedback"])
self.assertTrue(cohorts.is_course_cohorted(course.id))
self.assertFalse(
@@ -1345,42 +1345,52 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
config_course_cohorts(
course,
is_cohorted=True,
)
config_course_discussions(
course,
discussion_topics=["General", "Feedback"],
divided_discussions=["Feedback", "random_inline"]
)
self.assertFalse(
utils.is_commentable_divided(course.id, to_id("random")),
"By default, Non-top-level discussions are not cohorted in a cohorted courses."
)
# if always_cohort_inline_discussions is set to False, non-top-level discussion are always
# non cohorted unless they are explicitly set in cohorted_discussions
# if always_divide_inline_discussions is set to False, non-top-level discussion are always
# not divided unless they are explicitly set in divided_discussions
config_course_cohorts(
course,
is_cohorted=True,
)
config_course_discussions(
course,
discussion_topics=["General", "Feedback"],
divided_discussions=["Feedback", "random_inline"],
always_divide_inline_discussions=False
)
self.assertFalse(
utils.is_commentable_divided(course.id, to_id("random")),
"Non-top-level discussion is not cohorted if always_cohort_inline_discussions is False."
"Non-top-level discussion is not cohorted if always_divide_inline_discussions is False."
)
self.assertTrue(
utils.is_commentable_divided(course.id, to_id("random_inline")),
"If always_cohort_inline_discussions set to False, Non-top-level discussion is "
"If always_divide_inline_discussions set to False, Non-top-level discussion is "
"cohorted if explicitly set in cohorted_discussions."
)
self.assertTrue(
utils.is_commentable_divided(course.id, to_id("Feedback")),
"If always_cohort_inline_discussions set to False, top-level discussion are not affected."
"If always_divide_inline_discussions set to False, top-level discussion are not affected."
)
def test_is_commentable_divided_team(self):
course = modulestore().get_course(self.toy_course_key)
self.assertFalse(cohorts.is_course_cohorted(course.id))
config_course_cohorts(course, is_cohorted=True, always_divide_inline_discussions=True)
config_course_cohorts(course, is_cohorted=True)
config_course_discussions(course, always_divide_inline_discussions=True)
team = CourseTeamFactory(course_id=course.id)
# Verify that team discussions are not cohorted, but other discussions are

View File

@@ -125,6 +125,7 @@ def instructor_dashboard_2(request, course_id):
_section_course_info(course, access),
_section_membership(course, access, is_white_label),
_section_cohort_management(course, access),
_section_discussions_management(course, access),
_section_student_admin(course, access),
_section_data_download(course, access),
]
@@ -513,7 +514,6 @@ def _section_cohort_management(course, access):
),
'cohorts_url': reverse('cohorts', kwargs={'course_key_string': unicode(course_key)}),
'upload_cohorts_csv_url': reverse('add_users_to_cohorts', kwargs={'course_id': unicode(course_key)}),
'discussion_topics_url': reverse('cohort_discussion_topics', kwargs={'course_key_string': unicode(course_key)}),
'verified_track_cohorting_url': reverse(
'verified_track_cohorting', kwargs={'course_key_string': unicode(course_key)}
),
@@ -521,6 +521,21 @@ def _section_cohort_management(course, access):
return section_data
def _section_discussions_management(course, access):
""" Provide data for the corresponding discussion management section """
course_key = course.id
section_data = {
'section_key': 'discussions_management',
'section_display_name': _('Discussions'),
'discussion_topics_url': reverse('discussion_topics', kwargs={'course_key_string': unicode(course_key)}),
'course_discussion_settings': reverse(
'course_discussions_settings',
kwargs={'course_key_string': unicode(course_key)}
),
}
return section_data
def _is_small_course(course_key):
""" Compares against MAX_ENROLLMENT_INSTR_BUTTONS to determine if course enrollment is considered small. """
is_small_course = False

View File

@@ -1759,6 +1759,7 @@ REQUIRE_JS_PATH_OVERRIDES = {
'js/student_profile/views/learner_profile_factory': 'js/student_profile/views/learner_profile_factory.js',
'js/courseware/courseware_factory': 'js/courseware/courseware_factory.js',
'js/groups/views/cohorts_dashboard_factory': 'js/groups/views/cohorts_dashboard_factory.js',
'js/groups/discussions_management/discussions_dashboard_factory': 'js/discussions_management/views/discussions_dashboard_factory.js',
'draggabilly': 'js/vendor/draggabilly.js',
'hls': 'common/js/vendor/hls.js'
}

View File

@@ -1,12 +1,12 @@
(function(define) {
'use strict';
define(['backbone'], function(Backbone) {
var DiscussionTopicsSettingsModel = Backbone.Model.extend({
var CourseDiscussionTopicDetailsModel = Backbone.Model.extend({
defaults: {
course_wide_discussions: {},
inline_discussions: {}
}
});
return DiscussionTopicsSettingsModel;
return CourseDiscussionTopicDetailsModel;
});
}).call(this, define || RequireJS.define);

View File

@@ -0,0 +1,14 @@
(function(define) {
'use strict';
define(['backbone'], function(Backbone) {
var CourseDiscussionsSettingsModel = Backbone.Model.extend({
idAttribute: 'id',
defaults: {
divided_inline_discussions: [],
divided_course_wide_discussions: [],
always_divide_inline_discussions: false
}
});
return CourseDiscussionsSettingsModel;
});
}).call(this, define || RequireJS.define);

View File

@@ -0,0 +1,49 @@
(function(define) {
'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext',
'js/discussions_management/views/divided_discussions_inline',
'js/discussions_management/views/divided_discussions_course_wide',
'edx-ui-toolkit/js/utils/html-utils'
],
function($, _, Backbone, gettext, InlineDiscussionsView, CourseWideDiscussionsView, HtmlUtils) {
var DiscussionsView = Backbone.View.extend({
initialize: function(options) {
this.template = HtmlUtils.template($('#discussions-tpl').text());
this.context = options.context;
this.discussionSettings = options.discussionSettings;
},
render: function() {
HtmlUtils.setHtml(this.$el, this.template({}));
this.showDiscussionTopics();
return this;
},
getSectionCss: function(section) {
return ".instructor-nav .nav-item [data-section='" + section + "']";
},
showDiscussionTopics: function() {
var dividedDiscussionsElement = this.$('.discussions-nav');
if (!this.CourseWideDiscussionsView) {
this.CourseWideDiscussionsView = new CourseWideDiscussionsView({
el: dividedDiscussionsElement,
model: this.context.courseDiscussionTopicDetailsModel,
discussionSettings: this.discussionSettings
}).render();
}
if (!this.InlineDiscussionsView) {
this.InlineDiscussionsView = new InlineDiscussionsView({
el: dividedDiscussionsElement,
model: this.context.courseDiscussionTopicDetailsModel,
discussionSettings: this.discussionSettings
}).render();
}
}
});
return DiscussionsView;
});
}).call(this, define || RequireJS.define);

View File

@@ -0,0 +1,33 @@
(function(define) {
'use strict';
define('js/discussions_management/views/discussions_dashboard_factory',
['jquery', 'js/discussions_management/views/discussions',
'js/discussions_management/models/course_discussions_detail',
'js/discussions_management/models/course_discussions_settings'],
function($, DiscussionsView, CourseDiscussionTopicDetailsModel, CourseDiscussionsSettingsModel) {
return function() {
var courseDiscussionSettings = new CourseDiscussionsSettingsModel(),
discussionTopicsSettings = new CourseDiscussionTopicDetailsModel(),
$discussionsManagementElement = $('.discussions-management'),
discussionsView;
courseDiscussionSettings.url = $discussionsManagementElement.data('course-discussion-settings-url');
discussionTopicsSettings.url = $discussionsManagementElement.data('discussion-topics-url');
discussionsView = new DiscussionsView({
el: $discussionsManagementElement,
discussionSettings: courseDiscussionSettings,
context: {
courseDiscussionTopicDetailsModel: discussionTopicsSettings
}
});
courseDiscussionSettings.fetch().done(function() {
discussionTopicsSettings.fetch().done(function() {
discussionsView.render();
});
});
};
});
}).call(this, define || RequireJS.define);

View File

@@ -1,8 +1,9 @@
(function(define) {
'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/models/notification', 'js/views/notification'],
function($, _, Backbone) {
var CohortDiscussionConfigurationView = Backbone.View.extend({
function($, _, Backbone, gettext) {
/* global NotificationModel, NotificationView */
var DividedDiscussionConfigurationView = Backbone.View.extend({
/**
* Add/Remove the disabled attribute on given element.
@@ -14,53 +15,53 @@
},
/**
* Returns the cohorted discussions list.
* Returns the divided discussions list.
* @param {string} selector - To select the discussion elements whose ids to return.
* @returns {Array} - Cohorted discussions.
* @returns {Array} - Divided discussions.
*/
getCohortedDiscussions: function(selector) {
getDividedDiscussions: function(selector) {
var self = this,
cohortedDiscussions = [];
dividedDiscussions = [];
_.each(self.$(selector), function(topic) {
cohortedDiscussions.push($(topic).data('id'));
dividedDiscussions.push($(topic).data('id'));
});
return cohortedDiscussions;
return dividedDiscussions;
},
/**
* Save the cohortSettings' changed attributes to the server via PATCH method.
* Save the discussionSettings' changed attributes to the server via PATCH method.
* It shows the error message(s) if any.
* @param {object} $element - Messages would be shown before this element.
* @param {object} fieldData - Data to update on the server.
*/
saveForm: function($element, fieldData) {
var self = this,
cohortSettingsModel = this.cohortSettings,
discussionSettingsModel = this.discussionSettings,
saveOperation = $.Deferred(),
showErrorMessage;
showErrorMessage = function(message, $element) {
showErrorMessage = function(message) {
self.showMessage(message, $element, 'error');
};
this.removeNotification();
cohortSettingsModel.save(
discussionSettingsModel.save(
fieldData, {patch: true, wait: true}
).done(function() {
saveOperation.resolve();
}).fail(function(result) {
var errorMessage = null;
var errorMessage = null,
jsonResponse;
try {
var jsonResponse = JSON.parse(result.responseText);
jsonResponse = JSON.parse(result.responseText);
errorMessage = jsonResponse.error;
} catch (e) {
// Ignore the exception and show the default error message instead.
}
if (!errorMessage) {
errorMessage = gettext("We've encountered an error. Refresh your browser and then try again.");
errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); // eslint-disable-line max-len
}
showErrorMessage(errorMessage, $element);
showErrorMessage(errorMessage);
saveOperation.reject();
});
return saveOperation.promise();
@@ -92,6 +93,6 @@
}
});
return CohortDiscussionConfigurationView;
return DividedDiscussionConfigurationView;
});
}).call(this, define || RequireJS.define);

View File

@@ -1,21 +1,21 @@
(function(define) {
'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/groups/views/cohort_discussions',
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/discussions_management/views/divided_discussions',
'edx-ui-toolkit/js/utils/html-utils'],
function($, _, Backbone, gettext, CohortDiscussionConfigurationView, HtmlUtils) {
var CourseWideDiscussionsView = CohortDiscussionConfigurationView.extend({
function($, _, Backbone, gettext, DividedDiscussionConfigurationView, HtmlUtils) {
var CourseWideDiscussionsView = DividedDiscussionConfigurationView.extend({
events: {
'change .check-discussion-subcategory-course-wide': 'discussionCategoryStateChanged',
'click .cohort-course-wide-discussions-form .action-save': 'saveCourseWideDiscussionsForm'
},
initialize: function(options) {
this.template = HtmlUtils.template($('#cohort-discussions-course-wide-tpl').text());
this.cohortSettings = options.cohortSettings;
this.template = HtmlUtils.template($('#divided-discussions-course-wide-tpl').text());
this.discussionSettings = options.discussionSettings;
},
render: function() {
HtmlUtils.setHtml(this.$('.cohort-course-wide-discussions-nav'), this.template({
HtmlUtils.setHtml(this.$('.course-wide-discussions-nav'), this.template({
courseWideTopicsHtml: this.getCourseWideDiscussionsHtml(
this.model.get('course_wide_discussions')
)
@@ -56,25 +56,27 @@
},
/**
* Sends the cohorted_course_wide_discussions to the server and renders the view.
* Sends the courseWideDividedDiscussions to the server and renders the view.
*/
saveCourseWideDiscussionsForm: function(event) {
event.preventDefault();
var self = this,
courseWideCohortedDiscussions = self.getCohortedDiscussions(
courseWideDividedDiscussions = self.getDividedDiscussions(
'.check-discussion-subcategory-course-wide:checked'
),
fieldData = {cohorted_course_wide_discussions: courseWideCohortedDiscussions};
fieldData = {divided_course_wide_discussions: courseWideDividedDiscussions};
event.preventDefault();
self.saveForm(self.$('.course-wide-discussion-topics'), fieldData)
.done(function() {
self.model.fetch()
.done(function() {
self.render();
self.showMessage(gettext('Your changes have been saved.'), self.$('.course-wide-discussion-topics'));
self.showMessage(gettext('Your changes have been saved.'),
self.$('.course-wide-discussion-topics')
);
}).fail(function() {
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again.");
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); // eslint-disable-line max-len
self.showMessage(errorMessage, self.$('.course-wide-discussion-topics'), 'error');
});
});

View File

@@ -1,9 +1,9 @@
(function(define) {
'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/groups/views/cohort_discussions',
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/discussions_management/views/divided_discussions',
'edx-ui-toolkit/js/utils/html-utils', 'js/vendor/jquery.qubit'],
function($, _, Backbone, gettext, CohortDiscussionConfigurationView, HtmlUtils) {
var InlineDiscussionsView = CohortDiscussionConfigurationView.extend({
function($, _, Backbone, gettext, DividedDiscussionConfigurationView, HtmlUtils) {
var InlineDiscussionsView = DividedDiscussionConfigurationView.extend({
events: {
'change .check-discussion-category': 'setSaveButton',
'change .check-discussion-subcategory-inline': 'setSaveButton',
@@ -13,17 +13,19 @@
},
initialize: function(options) {
this.template = HtmlUtils.template($('#cohort-discussions-inline-tpl').text());
this.cohortSettings = options.cohortSettings;
this.template = HtmlUtils.template($('#divided-discussions-inline-tpl').text());
this.discussionSettings = options.discussionSettings;
},
render: function() {
var alwaysCohortInlineDiscussions = this.cohortSettings.get('always_cohort_inline_discussions'),
inline_discussions = this.model.get('inline_discussions');
var inlineDiscussions = this.model.get('inline_discussions'),
alwaysDivideInlineDiscussions = this.discussionSettings.get(
'always_divide_inline_discussions'
);
HtmlUtils.setHtml(this.$('.cohort-inline-discussions-nav'), this.template({
inlineDiscussionTopicsHtml: this.getInlineDiscussionsHtml(inline_discussions),
alwaysCohortInlineDiscussions: alwaysCohortInlineDiscussions
HtmlUtils.setHtml(this.$('.inline-discussions-nav'), this.template({
inlineDiscussionTopicsHtml: this.getInlineDiscussionsHtml(inlineDiscussions),
alwaysDivideInlineDiscussions: alwaysDivideInlineDiscussions
}));
// Provides the semantics for a nested list of tri-state checkboxes.
@@ -32,7 +34,7 @@
// based on the checked values of any checkboxes in child elements of the DOM.
this.$('ul.inline-topics').qubit();
this.setElementsEnabled(alwaysCohortInlineDiscussions, true);
this.setElementsEnabled(alwaysDivideInlineDiscussions, true);
},
/**
@@ -99,45 +101,48 @@
*
* Enable/Disable the category and sub-category checkboxes.
* Enable/Disable the save button.
* @param {bool} enable_checkboxes - The flag to enable/disable the checkboxes.
* @param {bool} enable_save_button - The flag to enable/disable the save button.
* @param {bool} enableCheckboxes - The flag to enable/disable the checkboxes.
* @param {bool} enableSaveButton - The flag to enable/disable the save button.
*/
setElementsEnabled: function(enable_checkboxes, enable_save_button) {
this.setDisabled(this.$('.check-discussion-category'), enable_checkboxes);
this.setDisabled(this.$('.check-discussion-subcategory-inline'), enable_checkboxes);
this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), enable_save_button);
setElementsEnabled: function(enableCheckboxes, enableSaveButton) {
this.setDisabled(this.$('.check-discussion-category'), enableCheckboxes);
this.setDisabled(this.$('.check-discussion-subcategory-inline'), enableCheckboxes);
this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), enableSaveButton);
},
/**
* Enables the save button for inline discussions.
*/
setSaveButton: function(event) {
setSaveButton: function() {
this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), false);
},
/**
* Sends the cohorted_inline_discussions to the server and renders the view.
* Sends the dividedInlineDiscussions to the server and renders the view.
*/
saveInlineDiscussionsForm: function(event) {
event.preventDefault();
var self = this,
cohortedInlineDiscussions = self.getCohortedDiscussions(
dividedInlineDiscussions = self.getDividedDiscussions(
'.check-discussion-subcategory-inline:checked'
),
fieldData = {
cohorted_inline_discussions: cohortedInlineDiscussions,
always_cohort_inline_discussions: self.$('.check-all-inline-discussions').prop('checked')
divided_inline_discussions: dividedInlineDiscussions,
always_divide_inline_discussions: self.$(
'.check-all-inline-discussions'
).prop('checked')
};
event.preventDefault();
self.saveForm(self.$('.inline-discussion-topics'), fieldData)
.done(function() {
self.model.fetch()
.done(function() {
self.render();
self.showMessage(gettext('Your changes have been saved.'), self.$('.inline-discussion-topics'));
self.showMessage(gettext('Your changes have been saved.'),
self.$('.inline-discussion-topics'));
}).fail(function() {
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again.");
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); // eslint-disable-line max-len
self.showMessage(errorMessage, self.$('.inline-discussion-topics'), 'error');
});
});

View File

@@ -4,10 +4,7 @@
var CourseCohortSettingsModel = Backbone.Model.extend({
idAttribute: 'id',
defaults: {
is_cohorted: false,
cohorted_inline_discussions: [],
cohorted_course_wide_discussions: [],
always_cohort_inline_discussions: false
is_cohorted: false
}
});
return CourseCohortSettingsModel;

View File

@@ -4,13 +4,11 @@
'js/groups/models/verified_track_settings',
'js/groups/views/cohort_editor', 'js/groups/views/cohort_form',
'js/groups/views/course_cohort_settings_notification',
'js/groups/views/cohort_discussions_inline', 'js/groups/views/cohort_discussions_course_wide',
'js/groups/views/verified_track_settings_notification',
'edx-ui-toolkit/js/utils/html-utils',
'js/views/file_uploader', 'js/models/notification', 'js/views/notification', 'string_utils'],
function($, _, Backbone, gettext, CohortModel, VerifiedTrackSettingsModel, CohortEditorView, CohortFormView,
CourseCohortSettingsNotificationView, InlineDiscussionsView, CourseWideDiscussionsView,
VerifiedTrackSettingsNotificationView, HtmlUtils) {
CourseCohortSettingsNotificationView, VerifiedTrackSettingsNotificationView, HtmlUtils) {
var hiddenClass = 'is-hidden',
disabledClass = 'is-disabled',
enableCohortsSelector = '.cohorts-state';
@@ -25,7 +23,6 @@
'click .cohort-management-add-form .action-cancel': 'cancelAddCohortForm',
'click .link-cross-reference': 'showSection',
'click .toggle-cohort-management-secondary': 'showCsvUpload',
'click .toggle-cohort-management-discussions': 'showDiscussionTopics'
},
initialize: function(options) {
@@ -306,27 +303,6 @@
}).render();
}
},
showDiscussionTopics: function(event) {
event.preventDefault();
$(event.currentTarget).addClass(hiddenClass);
var cohortDiscussionsElement = this.$('.cohort-discussions-nav').removeClass(hiddenClass);
if (!this.CourseWideDiscussionsView) {
this.CourseWideDiscussionsView = new CourseWideDiscussionsView({
el: cohortDiscussionsElement,
model: this.context.discussionTopicsSettingsModel,
cohortSettings: this.cohortSettings
}).render();
}
if (!this.InlineDiscussionsView) {
this.InlineDiscussionsView = new InlineDiscussionsView({
el: cohortDiscussionsElement,
model: this.context.discussionTopicsSettingsModel,
cohortSettings: this.cohortSettings
}).render();
}
},
getSectionCss: function(section) {
return ".instructor-nav .nav-item [data-section='" + section + "']";

View File

@@ -1,8 +1,8 @@
(function(define, undefined) {
'use strict';
define(['jquery', 'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/course_cohort_settings',
'js/groups/models/cohort_discussions', 'js/groups/models/content_group'],
function($, CohortsView, CohortCollection, CourseCohortSettingsModel, DiscussionTopicsSettingsModel, ContentGroupModel) {
'js/groups/models/content_group'],
function($, CohortsView, CohortCollection, CourseCohortSettingsModel, ContentGroupModel) {
return function(contentGroups, studioGroupConfigurationsUrl) {
var contentGroupModels = $.map(contentGroups, function(group) {
return new ContentGroupModel({
@@ -14,33 +14,27 @@
var cohorts = new CohortCollection(),
courseCohortSettings = new CourseCohortSettingsModel(),
discussionTopicsSettings = new DiscussionTopicsSettingsModel();
$cohortManagementElement = $('.cohort-management');
var cohortManagementElement = $('.cohort-management');
cohorts.url = cohortManagementElement.data('cohorts_url');
courseCohortSettings.url = cohortManagementElement.data('course_cohort_settings_url');
discussionTopicsSettings.url = cohortManagementElement.data('discussion-topics-url');
cohorts.url = $cohortManagementElement.data('cohorts_url');
courseCohortSettings.url = $cohortManagementElement.data('course_cohort_settings_url');
var cohortsView = new CohortsView({
el: cohortManagementElement,
el: $cohortManagementElement,
model: cohorts,
contentGroups: contentGroupModels,
cohortSettings: courseCohortSettings,
context: {
discussionTopicsSettingsModel: discussionTopicsSettings,
uploadCohortsCsvUrl: cohortManagementElement.data('upload_cohorts_csv_url'),
verifiedTrackCohortingUrl: cohortManagementElement.data('verified_track_cohorting_url'),
uploadCohortsCsvUrl: $cohortManagementElement.data('upload_cohorts_csv_url'),
verifiedTrackCohortingUrl: $cohortManagementElement.data('verified_track_cohorting_url'),
studioGroupConfigurationsUrl: studioGroupConfigurationsUrl,
isCcxEnabled: cohortManagementElement.data('is_ccx_enabled')
isCcxEnabled: $cohortManagementElement.data('is_ccx_enabled')
}
});
cohorts.fetch().done(function() {
courseCohortSettings.fetch().done(function() {
discussionTopicsSettings.fetch().done(function() {
cohortsView.render();
});
cohortsView.render();
});
});
};

View File

@@ -0,0 +1,12 @@
(function() {
'use strict';
function DiscussionsManagement($section) {
this.$section = $section;
this.$section.data('wrapper', this);
}
DiscussionsManagement.prototype.onClickTitle = function() {};
window.InstructorDashboard.sections.DiscussionsManagement = DiscussionsManagement;
}).call(this);

View File

@@ -187,6 +187,9 @@ such that the value can be defined later than this assignment (file load order).
}, {
constructor: window.InstructorDashboard.sections.CohortManagement,
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#cohort_management')
}, {
constructor: window.InstructorDashboard.sections.DiscussionsManagement,
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#discussions_management')
}, {
constructor: window.InstructorDashboard.sections.Certificates,
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#certificates')

View File

@@ -1,14 +1,11 @@
define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'common/js/spec_helpers/template_helpers',
'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/content_group',
'js/groups/models/course_cohort_settings', 'js/utils/animation', 'js/vendor/jquery.qubit',
'js/groups/views/course_cohort_settings_notification', 'js/groups/models/cohort_discussions',
'js/groups/views/cohort_discussions', 'js/groups/views/cohort_discussions_course_wide',
'js/groups/views/cohort_discussions_inline'
],
'common/js/spec_helpers/template_helpers',
'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/content_group',
'js/groups/models/course_cohort_settings', 'js/utils/animation', 'js/vendor/jquery.qubit',
'js/groups/views/course_cohort_settings_notification'
],
function(Backbone, $, AjaxHelpers, TemplateHelpers, CohortsView, CohortCollection, ContentGroupModel,
CourseCohortSettingsModel, AnimationUtil, Qubit, CourseCohortSettingsNotificationView, DiscussionTopicsSettingsModel,
CohortDiscussionsView, CohortCourseWideDiscussionsView, CohortInlineDiscussionsView) {
CourseCohortSettingsModel, AnimationUtil, Qubit, CourseCohortSettingsNotificationView) {
'use strict';
describe('Cohorts View', function() {
@@ -20,16 +17,7 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
getAddModal, selectContentGroup, clearContentGroup,
saveFormAndExpectErrors, createMockCohortSettings, MOCK_COHORTED_USER_PARTITION_ID,
MOCK_UPLOAD_COHORTS_CSV_URL, MOCK_STUDIO_ADVANCED_SETTINGS_URL, MOCK_STUDIO_GROUP_CONFIGURATIONS_URL,
MOCK_VERIFIED_TRACK_COHORTING_URL, MOCK_MANUAL_ASSIGNMENT, MOCK_RANDOM_ASSIGNMENT,
createMockCohortDiscussionsJson, createMockCohortDiscussions, showAndAssertDiscussionTopics;
// Selectors
var discussionsToggle = '.toggle-cohort-management-discussions',
inlineDiscussionsFormCss = '.cohort-inline-discussions-form',
courseWideDiscussionsFormCss = '.cohort-course-wide-discussions-form',
courseWideDiscussionsSaveButtonCss = '.cohort-course-wide-discussions-form .action-save',
inlineDiscussionsSaveButtonCss = '.cohort-inline-discussions-form .action-save',
inlineDiscussionsForm, courseWideDiscussionsForm;
MOCK_VERIFIED_TRACK_COHORTING_URL, MOCK_MANUAL_ASSIGNMENT, MOCK_RANDOM_ASSIGNMENT;
MOCK_MANUAL_ASSIGNMENT = 'manual';
MOCK_RANDOM_ASSIGNMENT = 'random';
@@ -70,66 +58,16 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
];
};
createMockCohortSettingsJson = function(isCohorted, cohortedInlineDiscussions, cohortedCourseWideDiscussions, alwaysCohortInlineDiscussions) {
createMockCohortSettingsJson = function(isCohorted) {
return {
id: 0,
is_cohorted: isCohorted || false,
cohorted_inline_discussions: cohortedInlineDiscussions || [],
cohorted_course_wide_discussions: cohortedCourseWideDiscussions || [],
always_cohort_inline_discussions: alwaysCohortInlineDiscussions || false
is_cohorted: isCohorted || false
};
};
createMockCohortSettings = function(isCohorted, cohortedInlineDiscussions, cohortedCourseWideDiscussions, alwaysCohortInlineDiscussions) {
createMockCohortSettings = function(isCohorted) {
return new CourseCohortSettingsModel(
createMockCohortSettingsJson(isCohorted, cohortedInlineDiscussions, cohortedCourseWideDiscussions, alwaysCohortInlineDiscussions)
);
};
createMockCohortDiscussionsJson = function(allCohorted) {
return {
course_wide_discussions: {
children: [['Topic_C_1', 'entry'], ['Topic_C_2', 'entry']],
entries: {
Topic_C_1: {
sort_key: null,
is_divided: true,
id: 'Topic_C_1'
},
Topic_C_2: {
sort_key: null,
is_divided: false,
id: 'Topic_C_2'
}
}
},
inline_discussions: {
subcategories: {
Topic_I_1: {
subcategories: {},
children: [['Inline_Discussion_1', 'entry'], ['Inline_Discussion_2', 'entry']],
entries: {
Inline_Discussion_1: {
sort_key: null,
is_divided: true,
id: 'Inline_Discussion_1'
},
Inline_Discussion_2: {
sort_key: null,
is_divided: allCohorted || false,
id: 'Inline_Discussion_2'
}
}
}
},
children: [['Topic_I_1', 'subcategory']]
}
};
};
createMockCohortDiscussions = function(allCohorted) {
return new DiscussionTopicsSettingsModel(
createMockCohortDiscussionsJson(allCohorted)
createMockCohortSettingsJson(isCohorted)
);
};
@@ -146,7 +84,7 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
};
createCohortsView = function(test, options) {
var cohortsJson, cohorts, contentGroups, cohortSettings, cohortDiscussions;
var cohortsJson, cohorts, contentGroups, cohortSettings;
options = options || {};
cohortsJson = options.cohorts ? {cohorts: options.cohorts} : createMockCohorts();
cohorts = new CohortCollection(cohortsJson, {parse: true});
@@ -155,16 +93,12 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
cohortSettings.url = '/mock_service/cohorts/settings';
cohorts.url = '/mock_service/cohorts';
cohortDiscussions = options.cohortDiscussions || createMockCohortDiscussions();
cohortDiscussions.url = '/mock_service/cohorts/discussion/topics';
requests = AjaxHelpers.requests(test);
cohortsView = new CohortsView({
model: cohorts,
contentGroups: contentGroups,
cohortSettings: cohortSettings,
context: {
discussionTopicsSettingsModel: cohortDiscussions,
uploadCohortsCsvUrl: MOCK_UPLOAD_COHORTS_CSV_URL,
studioAdvancedSettingsUrl: MOCK_STUDIO_ADVANCED_SETTINGS_URL,
studioGroupConfigurationsUrl: MOCK_STUDIO_GROUP_CONFIGURATIONS_URL,
@@ -314,40 +248,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
verifyDetailedMessage(expectedTitle, 'error', errors);
};
showAndAssertDiscussionTopics = function(that) {
createCohortsView(that);
// Should see the control to toggle cohort discussions.
expect(cohortsView.$(discussionsToggle)).not.toHaveClass('is-hidden');
// But discussions form should not be visible until toggle is clicked.
expect(cohortsView.$(inlineDiscussionsFormCss).length).toBe(0);
expect(cohortsView.$(courseWideDiscussionsFormCss).length).toBe(0);
expect(cohortsView.$(discussionsToggle).text()).
toContain('Specify whether discussion topics are divided by cohort');
cohortsView.$(discussionsToggle).click();
// After toggle is clicked, it should be hidden.
expect(cohortsView.$(discussionsToggle)).toHaveClass('is-hidden');
// Should see the course wide discussions form and its content
courseWideDiscussionsForm = cohortsView.$(courseWideDiscussionsFormCss);
expect(courseWideDiscussionsForm.length).toBe(1);
expect(courseWideDiscussionsForm.text()).
toContain('Course-Wide Discussion Topics');
expect(courseWideDiscussionsForm.text()).
toContain('Select the course-wide discussion topics that you want to divide by cohort.');
// Should see the inline discussions form and its content
inlineDiscussionsForm = cohortsView.$(inlineDiscussionsFormCss);
expect(inlineDiscussionsForm.length).toBe(1);
expect(inlineDiscussionsForm.text()).
toContain('Content-Specific Discussion Topics');
expect(inlineDiscussionsForm.text()).
toContain('Specify whether content-specific discussion topics are divided by cohort.');
};
unknownUserMessage = function(name) {
return 'Unknown user: ' + name;
};
@@ -364,10 +264,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-group-header');
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/notification');
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-state');
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-discussions-category');
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-discussions-subcategory');
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-discussions-course-wide');
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-discussions-inline');
TemplateHelpers.installTemplate('templates/file-upload');
});
@@ -381,8 +277,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
// If no cohorts have been created, can't upload a CSV file.
expect(cohortsView.$('.wrapper-cohort-supplemental')).toHaveClass('is-hidden');
// if no cohorts have been created, can't show the link to discussion topics.
expect(cohortsView.$('.cohort-discussions-nav')).toHaveClass('is-hidden');
});
it('syncs data when membership tab is clicked', function() {
@@ -422,10 +316,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
.toBe("Your file 'upload_file.txt' has been uploaded. Allow a few minutes for processing.");
});
it('can show discussion topics if cohort exists', function() {
showAndAssertDiscussionTopics(this);
});
describe('Cohort Selector', function() {
it('has no initial selection', function() {
createCohortsView(this);
@@ -1172,375 +1062,5 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
});
});
});
describe('Discussion Topics', function() {
var createCourseWideView, createInlineView,
inlineView, courseWideView, assertCohortedTopics;
createCourseWideView = function(that) {
createCohortsView(that);
courseWideView = new CohortCourseWideDiscussionsView({
el: cohortsView.$('.cohort-discussions-nav').removeClass('is-hidden'),
model: cohortsView.context.discussionTopicsSettingsModel,
cohortSettings: cohortsView.cohortSettings
});
courseWideView.render();
};
createInlineView = function(that, discussionTopicsSettingsModel) {
createCohortsView(that);
inlineView = new CohortInlineDiscussionsView({
el: cohortsView.$('.cohort-discussions-nav').removeClass('is-hidden'),
model: discussionTopicsSettingsModel || cohortsView.context.discussionTopicsSettingsModel,
cohortSettings: cohortsView.cohortSettings
});
inlineView.render();
};
assertCohortedTopics = function(view, type) {
expect(view.$('.check-discussion-subcategory-' + type).length).toBe(2);
expect(view.$('.check-discussion-subcategory-' + type + ':checked').length).toBe(1);
};
it('renders the view properly', function() {
showAndAssertDiscussionTopics(this);
});
describe('Course Wide', function() {
it('shows the "Save" button as disabled initially', function() {
createCourseWideView(this);
expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
});
it('has one cohorted and one non-cohorted topic', function() {
createCourseWideView(this);
assertCohortedTopics(courseWideView, 'course-wide');
expect(courseWideView.$('.cohorted-text').length).toBe(2);
expect(courseWideView.$('.cohorted-text.hidden').length).toBe(1);
});
it('enables the "Save" button after changing checkbox', function() {
createCourseWideView(this);
// save button is disabled.
expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
$(courseWideView.$('.check-discussion-subcategory-course-wide')[0]).prop('checked', false).change();
// save button is enabled.
expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
});
it('saves the topic successfully', function() {
createCourseWideView(this);
$(courseWideView.$('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change();
expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
// Save the updated settings
courseWideView.$('.action-save').click();
// fake requests for cohort settings with PATCH method.
AjaxHelpers.expectJsonRequest(
requests, 'PATCH', '/mock_service/cohorts/settings',
{cohorted_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
);
AjaxHelpers.respondWithJson(
requests,
{cohorted_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
);
// fake request for discussion/topics with GET method.
AjaxHelpers.expectJsonRequest(
requests, 'GET', '/mock_service/cohorts/discussion/topics'
);
AjaxHelpers.respondWithJson(
requests,
createMockCohortDiscussions()
);
// verify the success message.
expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
verifyMessage('Your changes have been saved.', 'confirmation');
});
it('shows an appropriate message when subsequent "GET" returns HTTP500', function() {
createCourseWideView(this);
$(courseWideView.$('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change();
expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
// Save the updated settings
courseWideView.$('.action-save').click();
// fake requests for cohort settings with PATCH method.
AjaxHelpers.expectJsonRequest(
requests, 'PATCH', '/mock_service/cohorts/settings',
{cohorted_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
);
AjaxHelpers.respondWithJson(
requests,
{cohorted_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
);
// fake request for discussion/topics with GET method.
AjaxHelpers.expectJsonRequest(
requests, 'GET', '/mock_service/cohorts/discussion/topics'
);
AjaxHelpers.respondWithError(requests, 500);
var expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
expect(courseWideView.$('.message-title').text().trim()).toBe(expectedTitle);
});
it('shows an appropriate error message for HTTP500', function() {
createCourseWideView(this);
$(courseWideView.$('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change();
courseWideView.$('.action-save').click();
AjaxHelpers.respondWithError(requests, 500);
var expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
expect(courseWideView.$('.message-title').text().trim()).toBe(expectedTitle);
});
});
describe('Inline', function() {
var enableSaveButton, mockGetRequest, verifySuccess, mockPatchRequest;
enableSaveButton = function() {
// enable the inline discussion topics.
inlineView.$('.check-cohort-inline-discussions').prop('checked', 'checked').change();
$(inlineView.$('.check-discussion-subcategory-inline')[0]).prop('checked', 'checked').change();
expect(inlineView.$(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
};
verifySuccess = function() {
// verify the success message.
expect(inlineView.$(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
verifyMessage('Your changes have been saved.', 'confirmation');
};
mockPatchRequest = function(cohortedInlineDiscussions) {
AjaxHelpers.expectJsonRequest(
requests, 'PATCH', '/mock_service/cohorts/settings',
{
cohorted_inline_discussions: cohortedInlineDiscussions,
always_cohort_inline_discussions: false
}
);
AjaxHelpers.respondWithJson(
requests,
{
cohorted_inline_discussions: cohortedInlineDiscussions,
always_cohort_inline_discussions: false
}
);
};
mockGetRequest = function(allCohorted) {
// fake request for discussion/topics with GET method.
AjaxHelpers.expectJsonRequest(
requests, 'GET', '/mock_service/cohorts/discussion/topics'
);
AjaxHelpers.respondWithJson(
requests,
createMockCohortDiscussions(allCohorted)
);
};
it('shows the "Save" button as disabled initially', function() {
createInlineView(this);
expect(inlineView.$(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
});
it('shows always cohort radio button as selected', function() {
createInlineView(this);
inlineView.$('.check-all-inline-discussions').prop('checked', 'checked').change();
// verify always cohort inline discussions is being selected.
expect(inlineView.$('.check-all-inline-discussions').prop('checked')).toBeTruthy();
// verify that inline topics are disabled
expect(inlineView.$('.check-discussion-subcategory-inline').prop('disabled')).toBeTruthy();
expect(inlineView.$('.check-discussion-category').prop('disabled')).toBeTruthy();
// verify that cohort some topics are not being selected.
expect(inlineView.$('.check-cohort-inline-discussions').prop('checked')).toBeFalsy();
});
it('shows cohort some topics radio button as selected', function() {
createInlineView(this);
inlineView.$('.check-cohort-inline-discussions').prop('checked', 'checked').change();
// verify some cohort inline discussions radio is being selected.
expect(inlineView.$('.check-cohort-inline-discussions').prop('checked')).toBeTruthy();
// verify always cohort radio is not selected.
expect(inlineView.$('.check-all-inline-discussions').prop('checked')).toBeFalsy();
// verify that inline topics are enabled
expect(inlineView.$('.check-discussion-subcategory-inline').prop('disabled')).toBeFalsy();
expect(inlineView.$('.check-discussion-category').prop('disabled')).toBeFalsy();
});
it('has cohorted and non-cohorted topics', function() {
createInlineView(this);
enableSaveButton();
assertCohortedTopics(inlineView, 'inline');
});
it('enables "Save" button after changing from always inline option', function() {
createInlineView(this);
enableSaveButton();
});
it('saves the topic', function() {
createInlineView(this);
enableSaveButton();
// Save the updated settings
inlineView.$('.action-save').click();
mockPatchRequest(['Inline_Discussion_1']);
mockGetRequest();
verifySuccess();
});
it('selects the parent category when all children are selected', function() {
createInlineView(this);
enableSaveButton();
// parent category should be indeterminate.
expect(inlineView.$('.check-discussion-category:checked').length).toBe(0);
expect(inlineView.$('.check-discussion-category:indeterminate').length).toBe(1);
inlineView.$('.check-discussion-subcategory-inline').prop('checked', 'checked').change();
// parent should be checked as we checked all children
expect(inlineView.$('.check-discussion-category:checked').length).toBe(1);
});
it('selects/deselects all children when a parent category is selected/deselected', function() {
createInlineView(this);
enableSaveButton();
expect(inlineView.$('.check-discussion-category:checked').length).toBe(0);
inlineView.$('.check-discussion-category').prop('checked', 'checked').change();
expect(inlineView.$('.check-discussion-category:checked').length).toBe(1);
expect(inlineView.$('.check-discussion-subcategory-inline:checked').length).toBe(2);
// un-check the parent, all children should be unchecd.
inlineView.$('.check-discussion-category').prop('checked', false).change();
expect(inlineView.$('.check-discussion-category:checked').length).toBe(0);
expect(inlineView.$('.check-discussion-subcategory-inline:checked').length).toBe(0);
});
it('saves correctly when a subset of topics are selected within a category', function() {
createInlineView(this);
enableSaveButton();
// parent category should be indeterminate.
expect(inlineView.$('.check-discussion-category:checked').length).toBe(0);
expect(inlineView.$('.check-discussion-category:indeterminate').length).toBe(1);
// Save the updated settings
inlineView.$('.action-save').click();
mockPatchRequest(['Inline_Discussion_1']);
mockGetRequest();
verifySuccess();
// parent category should be indeterminate.
expect(inlineView.$('.check-discussion-category:indeterminate').length).toBe(1);
});
it('saves correctly when all child topics are selected within a category', function() {
createInlineView(this);
enableSaveButton();
// parent category should be indeterminate.
expect(inlineView.$('.check-discussion-category:checked').length).toBe(0);
expect(inlineView.$('.check-discussion-category:indeterminate').length).toBe(1);
inlineView.$('.check-discussion-subcategory-inline').prop('checked', 'checked').change();
// Save the updated settings
inlineView.$('.action-save').click();
mockPatchRequest(['Inline_Discussion_1', 'Inline_Discussion_2']);
mockGetRequest(true);
verifySuccess();
// parent category should be checked.
expect(inlineView.$('.check-discussion-category:checked').length).toBe(1);
});
it('shows an appropriate message when no inline topics exist', function() {
var topicsJson, discussionTopicsSettingsModel;
topicsJson = {
course_wide_discussions: {
children: [['Topic_C_1', 'entry']],
entries: {
Topic_C_1: {
sort_key: null,
is_divided: true,
id: 'Topic_C_1'
}
}
},
inline_discussions: {
subcategories: {},
children: []
}
};
discussionTopicsSettingsModel = new DiscussionTopicsSettingsModel(topicsJson);
createInlineView(this, discussionTopicsSettingsModel);
var expectedTitle = 'No content-specific discussion topics exist.';
expect(inlineView.$('.no-topics').text().trim()).toBe(expectedTitle);
});
it('shows an appropriate message when subsequent "GET" returns HTTP500', function() {
createInlineView(this);
enableSaveButton();
// Save the updated settings
inlineView.$('.action-save').click();
mockPatchRequest(['Inline_Discussion_1']);
// fake request for discussion/topics with GET method.
AjaxHelpers.expectJsonRequest(
requests, 'GET', '/mock_service/cohorts/discussion/topics'
);
AjaxHelpers.respondWithError(requests, 500);
var expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
expect(inlineView.$('.message-title').text().trim()).toBe(expectedTitle);
});
it('shows an appropriate error message for HTTP500', function() {
createInlineView(this);
enableSaveButton();
$(inlineView.$('.check-discussion-subcategory-inline')[1]).prop('checked', 'checked').change();
inlineView.$('.action-save').click();
AjaxHelpers.respondWithError(requests, 500);
var expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
expect(inlineView.$('.message-title').text().trim()).toBe(expectedTitle);
});
});
});
});
});

View File

@@ -0,0 +1,512 @@
define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'common/js/spec_helpers/template_helpers',
'js/discussions_management/views/discussions', 'js/discussions_management/models/course_discussions_detail',
'js/discussions_management/models/course_discussions_settings'
],
function(Backbone, $, AjaxHelpers, TemplateHelpers, DiscussionsView, CourseDiscussionTopicDetailsModel,
CourseDiscussionsSettingsModel) {
'use strict';
describe('Discussions View', function() {
var createMockDiscussionsSettingsJson, createDiscussionsView, discussionsView, requests, verifyMessage,
createMockDiscussionsSettings, createMockDiscussionsJson, createMockDiscussions,
showAndAssertDiscussionTopics;
// Selectors
var inlineDiscussionsFormCss = '.cohort-inline-discussions-form',
courseWideDiscussionsFormCss = '.cohort-course-wide-discussions-form',
courseWideDiscussionsSaveButtonCss = '.cohort-course-wide-discussions-form .action-save',
inlineDiscussionsSaveButtonCss = '.cohort-inline-discussions-form .action-save';
createMockDiscussionsSettingsJson = function(dividedInlineDiscussions,
dividedCourseWideDiscussions,
alwaysDivideInlineDiscussions) {
return {
id: 0,
divided_inline_discussions: dividedInlineDiscussions || [],
divided_course_wide_discussions: dividedCourseWideDiscussions || [],
always_divide_inline_discussions: alwaysDivideInlineDiscussions || false
};
};
createMockDiscussionsSettings = function(dividedInlineDiscussions,
dividedCourseWideDiscussions,
alwaysDivideInlineDiscussions) {
return new CourseDiscussionsSettingsModel(
createMockDiscussionsSettingsJson(dividedInlineDiscussions,
dividedCourseWideDiscussions,
alwaysDivideInlineDiscussions)
);
};
createMockDiscussionsJson = function(allDivided) {
return {
course_wide_discussions: {
children: [['Topic_C_1', 'entry'], ['Topic_C_2', 'entry']],
entries: {
Topic_C_1: {
sort_key: null,
is_divided: true,
id: 'Topic_C_1'
},
Topic_C_2: {
sort_key: null,
is_divided: false,
id: 'Topic_C_2'
}
}
},
inline_discussions: {
subcategories: {
Topic_I_1: {
subcategories: {},
children: [['Inline_Discussion_1', 'entry'], ['Inline_Discussion_2', 'entry']],
entries: {
Inline_Discussion_1: {
sort_key: null,
is_divided: true,
id: 'Inline_Discussion_1'
},
Inline_Discussion_2: {
sort_key: null,
is_divided: allDivided || false,
id: 'Inline_Discussion_2'
}
}
}
},
children: [['Topic_I_1', 'subcategory']]
}
};
};
createMockDiscussions = function(allDivided) {
return new CourseDiscussionTopicDetailsModel(
createMockDiscussionsJson(allDivided)
);
};
verifyMessage = function(expectedTitle, expectedMessageType, expectedAction, hasDetails) {
expect(discussionsView.$('.message-title').text().trim()).toBe(expectedTitle);
expect(discussionsView.$('div.message')).toHaveClass('message-' + expectedMessageType);
if (expectedAction) {
expect(discussionsView.$('.message-actions .action-primary').text().trim()).toBe(expectedAction);
} else {
expect(discussionsView.$('.message-actions .action-primary').length).toBe(0);
} if (!hasDetails) {
expect(discussionsView.$('.summary-items').length).toBe(0);
}
};
createDiscussionsView = function(test, options) {
var discussionSettings, dividedDiscussions, discussionOptions;
discussionOptions = options || {};
discussionSettings = discussionOptions.cohortSettings || createMockDiscussionsSettings();
discussionSettings.url = '/mock_service/discussions/settings';
dividedDiscussions = discussionOptions.dividedDiscussions || createMockDiscussions();
dividedDiscussions.url = '/mock_service/discussion/topics';
requests = AjaxHelpers.requests(test);
discussionsView = new DiscussionsView({
el: $('.discussions-management'),
discussionSettings: discussionSettings,
context: {
courseDiscussionTopicDetailsModel: dividedDiscussions
}
});
discussionsView.render();
};
showAndAssertDiscussionTopics = function() {
var $courseWideDiscussionsForm,
$inlineDiscussionsForm;
$courseWideDiscussionsForm = discussionsView.$(courseWideDiscussionsFormCss);
$inlineDiscussionsForm = discussionsView.$(inlineDiscussionsFormCss);
// Discussions form should not be visible.
expect($inlineDiscussionsForm.length).toBe(1);
expect($courseWideDiscussionsForm.length).toBe(1);
expect($courseWideDiscussionsForm.text()).
toContain('Course-Wide Discussion Topics');
expect($courseWideDiscussionsForm.text()).
toContain('Select the course-wide discussion topics that you want to divide by cohort.');
// Should see the inline discussions form and its content
expect($inlineDiscussionsForm.length).toBe(1);
expect($inlineDiscussionsForm.text()).
toContain('Content-Specific Discussion Topics');
expect($inlineDiscussionsForm.text()).
toContain('Specify whether content-specific discussion topics are divided by cohort.');
};
beforeEach(function() {
setFixtures('<ul class="instructor-nav">' +
'<li class="nav-item"><button type="button" data-section="discussion_management" ' +
'class="active-section">Discussions</button></li></ul><div></div>' +
'<div class="discussions-management"></div>');
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/discussions');
TemplateHelpers.installTemplate(
'templates/instructor/instructor_dashboard_2/divided-discussions-course-wide'
);
TemplateHelpers.installTemplate(
'templates/instructor/instructor_dashboard_2/divided-discussions-inline'
);
TemplateHelpers.installTemplate(
'templates/instructor/instructor_dashboard_2/cohort-discussions-category'
);
TemplateHelpers.installTemplate(
'templates/instructor/instructor_dashboard_2/cohort-discussions-subcategory'
);
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/notification');
});
describe('Discussion Topics', function() {
var courseWideView, assertDividedTopics;
assertDividedTopics = function(view, type) {
expect($('.check-discussion-subcategory-' + type).length).toBe(2);
expect($('.check-discussion-subcategory-' + type + ':checked').length).toBe(1);
};
it('renders the view properly', function() {
createDiscussionsView(this);
showAndAssertDiscussionTopics(this);
});
describe('Course Wide', function() {
it('shows the "Save" button as disabled initially', function() {
createDiscussionsView(this);
expect($(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
});
it('has one divided and one non-divided topic', function() {
createDiscussionsView(this);
assertDividedTopics(courseWideView, 'course-wide');
expect($('.course-wide-discussion-topics .divided-discussion-text').length).toBe(2);
expect($('.course-wide-discussion-topics .divided-discussion-text.hidden').length).toBe(1);
});
it('enables the "Save" button after changing checkbox', function() {
createDiscussionsView(this);
// save button is disabled.
expect($(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
$($('.check-discussion-subcategory-course-wide')[0]).prop('checked', false).change();
// save button is enabled.
expect($(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
});
it('saves the topic successfully', function() {
createDiscussionsView(this);
$($('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change();
expect($(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
// Save the updated settings
$('#cohort-course-wide-discussions-form .action-save').click();
// fake requests for discussions settings with PATCH method.
AjaxHelpers.expectJsonRequest(
requests, 'PATCH', '/mock_service/discussions/settings',
{divided_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
);
AjaxHelpers.respondWithJson(
requests,
{divided_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
);
// fake request for discussion/topics with GET method.
AjaxHelpers.expectJsonRequest(
requests, 'GET', '/mock_service/discussion/topics'
);
AjaxHelpers.respondWithJson(
requests,
createMockDiscussions()
);
// verify the success message.
expect($(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
verifyMessage('Your changes have been saved.', 'confirmation');
});
it('shows an appropriate message when subsequent "GET" returns HTTP500', function() {
var expectedTitle;
createDiscussionsView(this);
$($('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change();
expect($(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
// Save the updated settings
$('#cohort-course-wide-discussions-form .action-save').click();
// fake requests for discussion settings with PATCH method.
AjaxHelpers.expectJsonRequest(
requests, 'PATCH', '/mock_service/discussions/settings',
{divided_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
);
AjaxHelpers.respondWithJson(
requests,
{divided_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
);
// fake request for discussion/topics with GET method.
AjaxHelpers.expectJsonRequest(
requests, 'GET', '/mock_service/discussion/topics'
);
AjaxHelpers.respondWithError(requests, 500);
expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
expect($('.message-title').text().trim()).toBe(expectedTitle);
});
it('shows an appropriate error message for HTTP500', function() {
var expectedTitle;
createDiscussionsView(this);
$($('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change();
$('.action-save').click();
AjaxHelpers.respondWithError(requests, 500);
expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
expect($('.message-title').text().trim()).toBe(expectedTitle);
});
});
describe('Inline', function() {
var enableSaveButton, mockGetRequest, verifySuccess, mockPatchRequest;
enableSaveButton = function() {
// enable the inline discussion topics.
$('.check-cohort-inline-discussions').prop('checked', 'checked').change();
$($('.check-discussion-subcategory-inline')[0]).prop('checked', 'checked').change();
expect($(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
};
verifySuccess = function() {
// verify the success message.
expect($(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
verifyMessage('Your changes have been saved.', 'confirmation');
};
mockPatchRequest = function(dividededInlineDiscussions) {
AjaxHelpers.expectJsonRequest(
requests, 'PATCH', '/mock_service/discussions/settings',
{
divided_inline_discussions: dividededInlineDiscussions,
always_divide_inline_discussions: false
}
);
AjaxHelpers.respondWithJson(
requests,
{
divided_inline_discussions: dividededInlineDiscussions,
always_divide_inline_discussions: false
}
);
};
mockGetRequest = function(alldivided) {
// fake request for discussion/topics with GET method.
AjaxHelpers.expectJsonRequest(
requests, 'GET', '/mock_service/discussion/topics'
);
AjaxHelpers.respondWithJson(
requests,
createMockDiscussions(alldivided)
);
};
it('shows the "Save" button as disabled initially', function() {
createDiscussionsView(this);
expect($(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
});
it('shows always divide radio button as selected', function() {
createDiscussionsView(this);
$('.check-all-inline-discussions').prop('checked', 'checked').change();
// verify always divide inline discussions is being selected.
expect($('.check-all-inline-discussions').prop('checked')).toBeTruthy();
// verify that inline topics are disabled
expect($('.check-discussion-subcategory-inline').prop('disabled')).toBeTruthy();
expect($('.check-discussion-category').prop('disabled')).toBeTruthy();
// verify that divide some topics are not being selected.
expect($('.check-cohort-inline-discussions').prop('checked')).toBeFalsy();
});
it('shows divide some topics radio button as selected', function() {
createDiscussionsView(this);
$('.check-cohort-inline-discussions').prop('checked', 'checked').change();
// verify some divide inline discussions radio is being selected.
expect($('.check-cohort-inline-discussions').prop('checked')).toBeTruthy();
// verify always divide radio is not selected.
expect($('.check-all-inline-discussions').prop('checked')).toBeFalsy();
// verify that inline topics are enabled
expect($('.check-discussion-subcategory-inline').prop('disabled')).toBeFalsy();
expect($('.check-discussion-category').prop('disabled')).toBeFalsy();
});
it('has divided and non-divided topics', function() {
createDiscussionsView(this);
enableSaveButton();
assertDividedTopics(this, 'inline');
});
it('enables "Save" button after changing from always inline option', function() {
createDiscussionsView(this);
enableSaveButton();
});
it('saves the topic', function() {
createDiscussionsView(this);
enableSaveButton();
// Save the updated settings
$('.action-save').click();
mockPatchRequest(['Inline_Discussion_1']);
mockGetRequest();
verifySuccess();
});
it('selects the parent category when all children are selected', function() {
createDiscussionsView(this);
enableSaveButton();
// parent category should be indeterminate.
expect($('.check-discussion-category:checked').length).toBe(0);
expect($('.check-discussion-category:indeterminate').length).toBe(1);
$('.check-discussion-subcategory-inline').prop('checked', 'checked').change();
// parent should be checked as we checked all children
expect($('.check-discussion-category:checked').length).toBe(1);
});
it('selects/deselects all children when a parent category is selected/deselected', function() {
createDiscussionsView(this);
enableSaveButton();
expect($('.check-discussion-category:checked').length).toBe(0);
$('.check-discussion-category').prop('checked', 'checked').change();
expect($('.check-discussion-category:checked').length).toBe(1);
expect($('.check-discussion-subcategory-inline:checked').length).toBe(2);
// un-check the parent, all children should be unchecd.
$('.check-discussion-category').prop('checked', false).change();
expect($('.check-discussion-category:checked').length).toBe(0);
expect($('.check-discussion-subcategory-inline:checked').length).toBe(0);
});
it('saves correctly when a subset of topics are selected within a category', function() {
createDiscussionsView(this);
enableSaveButton();
// parent category should be indeterminate.
expect($('.check-discussion-category:checked').length).toBe(0);
expect($('.check-discussion-category:indeterminate').length).toBe(1);
// Save the updated settings
$('#cohort-inline-discussions-form .action-save').click();
mockPatchRequest(['Inline_Discussion_1']);
mockGetRequest();
verifySuccess();
// parent category should be indeterminate.
expect($('.check-discussion-category:indeterminate').length).toBe(1);
});
it('saves correctly when all child topics are selected within a category', function() {
createDiscussionsView(this);
enableSaveButton();
// parent category should be indeterminate.
expect($('.check-discussion-category:checked').length).toBe(0);
expect($('.check-discussion-category:indeterminate').length).toBe(1);
$('.check-discussion-subcategory-inline').prop('checked', 'checked').change();
// Save the updated settings
$('#cohort-inline-discussions-form .action-save').click();
mockPatchRequest(['Inline_Discussion_1', 'Inline_Discussion_2']);
mockGetRequest(true);
verifySuccess();
// parent category should be checked.
expect($('.check-discussion-category:checked').length).toBe(1);
});
it('shows an appropriate message when no inline topics exist', function() {
var topicsJson, options, expectedTitle;
topicsJson = {
course_wide_discussions: {
children: [['Topic_C_1', 'entry']],
entries: {
Topic_C_1: {
sort_key: null,
is_divided: true,
id: 'Topic_C_1'
}
}
},
inline_discussions: {
subcategories: {},
children: []
}
};
options = {dividedDiscussions: new CourseDiscussionTopicDetailsModel(topicsJson)};
createDiscussionsView(this, options);
expectedTitle = 'No content-specific discussion topics exist.';
expect($('.no-topics').text().trim()).toBe(expectedTitle);
});
it('shows an appropriate message when subsequent "GET" returns HTTP500', function() {
var expectedTitle;
createDiscussionsView(this);
enableSaveButton();
// Save the updated settings
$('#cohort-inline-discussions-form .action-save').click();
mockPatchRequest(['Inline_Discussion_1']);
// fake request for discussion/topics with GET method.
AjaxHelpers.expectJsonRequest(
requests, 'GET', '/mock_service/discussion/topics'
);
AjaxHelpers.respondWithError(requests, 500);
expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
expect($('.message-title').text().trim()).toBe(expectedTitle);
});
it('shows an appropriate error message for HTTP500', function() {
var expectedTitle;
createDiscussionsView(this);
enableSaveButton();
$($('.check-discussion-subcategory-inline')[1]).prop('checked', 'checked').change();
$('#cohort-inline-discussions-form .action-save').click();
AjaxHelpers.respondWithError(requests, 500);
expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
expect($('.message-title').text().trim()).toBe(expectedTitle);
});
});
});
});
});

View File

@@ -28,6 +28,7 @@
'js/edxnotes/views/page_factory',
'js/financial-assistance/financial_assistance_form_factory',
'js/groups/views/cohorts_dashboard_factory',
'js/discussions_management/views/discussions_dashboard_factory',
'js/header_factory',
'js/learner_dashboard/program_details_factory',
'js/learner_dashboard/program_list_factory',

View File

@@ -731,6 +731,7 @@
'js/spec/edxnotes/views/visibility_decorator_spec.js',
'js/spec/financial-assistance/financial_assistance_form_view_spec.js',
'js/spec/groups/views/cohorts_spec.js',
'js/spec/groups/views/discussions_spec.js',
'js/spec/instructor_dashboard/certificates_bulk_exception_spec.js',
'js/spec/instructor_dashboard/certificates_exception_spec.js',
'js/spec/instructor_dashboard/certificates_invalidation_spec.js',

View File

@@ -1181,49 +1181,6 @@
}
}
// cohort discussions interface.
.cohort-discussions-nav {
.cohort-course-wide-discussions-form {
.form-actions {
padding-top: ($baseline/2);
}
}
.category-title,
.topic-name,
.all-inline-discussions,
.always_cohort_inline_discussions,
.cohort_inline_discussions {
padding-left: ($baseline/2);
}
.always_cohort_inline_discussions,
.cohort_inline_discussions {
padding-top: ($baseline/2);
}
.category-item,
.subcategory-item {
padding-top: ($baseline/2);
}
.cohorted-text {
color: $uxpl-blue-base;
}
.discussions-wrapper {
@extend %ui-no-list;
padding: 0 ($baseline/2);
.subcategories {
padding: 0 ($baseline*1.5);
}
}
}
.wrapper-tabs { // This applies to the tab-like interface that toggles between the student management and the group settings
@extend %ui-no-list;
@extend %ui-depth1;
@@ -1280,6 +1237,77 @@
}
}
// view - discussions management
// --------------------
.instructor-dashboard-wrapper-2 section.idash-section#discussions_management {
.form-submit {
@include idashbutton($uxpl-blue-base);
@include font-size(14);
@include line-height(14);
margin-right: ($baseline/2);
margin-bottom: 0;
text-shadow: none;
}
.discussions-management-supplemental {
@extend %t-copy-sub1;
margin-top: $baseline;
padding: ($baseline/2) $baseline;
background: $gray-l6;
border-radius: ($baseline/10);
}
// cohort discussions interface.
.discussions-nav {
.cohort-course-wide-discussions-form {
.form-actions {
padding-top: ($baseline/2);
}
}
.category-title,
.topic-name,
.all-inline-discussions,
.always_divide_inline_discussions,
.divide_inline_discussions {
padding-left: ($baseline/2);
}
.always_divide_inline_discussions,
.divide_inline_discussions {
padding-top: ($baseline/2);
}
.category-item,
.subcategory-item {
padding-top: ($baseline/2);
}
.divided-discussion-text{
color: $uxpl-blue-base;
}
.discussions-wrapper {
@extend %ui-no-list;
padding: 0 ($baseline/2);
.subcategories {
padding: 0 ($baseline*1.5);
}
}
}
.wrapper-tabs {
@extend %ui-no-list;
@extend %ui-depth1;
position: relative;
top: 1px;
padding: 0 $baseline;
}
}
// view - student admin
// --------------------

View File

@@ -3,7 +3,7 @@
<label>
<input data-id="<%- id %>" class="check-discussion-subcategory-<%- type %>" type="checkbox" <%- is_divided ? 'checked="checked"' : '' %> />
<span class="topic-name"><%- name %></span>
<span class="cohorted-text <%- is_divided ? '' : 'hidden'%>">- <%- gettext('Cohorted') %></span>
<span class="divided-discussion-text <%- is_divided ? '' : 'hidden'%>">- <%- gettext('Divided') %></span>
</label>
</div>
</li>

View File

@@ -13,7 +13,6 @@ from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_
data-cohorts_url="${section_data['cohorts_url']}"
data-upload_cohorts_csv_url="${section_data['upload_cohorts_csv_url']}"
data-course_cohort_settings_url="${section_data['course_cohort_settings_url']}"
data-discussion-topics-url="${section_data['discussion_topics_url']}"
data-verified_track_cohorting_url="${section_data['verified_track_cohorting_url']}"
data-is_ccx_enabled="${'true' if section_data['ccx_is_enabled'] else 'false'}"
>

View File

@@ -49,15 +49,5 @@
%>
</p>
</div>
<hr class="divider divider-lv1" />
<!-- Discussion Topics. -->
<button class="toggle-cohort-management-discussions" data-href="#cohort-discussions-management"><%- gettext('Specify whether discussion topics are divided by cohort') %></button>
<div class="cohort-discussions-nav is-hidden" id="cohort-discussions-management" tabindex="-1">
<div class="cohort-course-wide-discussions-nav"></div>
<div class="cohort-inline-discussions-nav"></div>
</div>
</div>
<% } %>

View File

@@ -0,0 +1,5 @@
<!-- Discussion Topics. -->
<div class="discussions-nav" id="discussions-management" tabindex="-1">
<div class="course-wide-discussions-nav"></div>
<div class="inline-discussions-nav"></div>
</div>

View File

@@ -0,0 +1,22 @@
<%page expression_filter="h" args="section_data"/>
<%namespace name='static' file='../../static_content.html'/>
<%!
from django.utils.translation import ugettext as _
from openedx.core.djangolib.js_utils import js_escaped_string, dump_js_escaped_json
from courseware.courses import get_studio_url
from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_user_partition
%>
<div class="discussions-management"
data-discussion-topics-url="${section_data['discussion_topics_url']}"
data-course-discussion-settings-url="${section_data['course_discussion_settings']}"
>
</div>
<%block name="js_extra">
<%static:require_module module_name="js/discussions_management/views/discussions_dashboard_factory" class_name="DiscussionsFactory">
DiscussionsFactory();
</%static:require_module>
</%block>

View File

@@ -1,6 +1,6 @@
<h3 class="hd hd-3 subsection-title"><%- gettext('Specify whether discussion topics are divided by cohort') %></h3>
<form action="" method="post" id="cohort-course-wide-discussions-form" class="cohort-course-wide-discussions-form">
<div class="wrapper cohort-management-supplemental">
<div class="wrapper discussions-management-supplemental">
<div class="form-fields">
<div class="form-field">
<div class="course-wide-discussion-topics">

View File

@@ -1,21 +1,21 @@
<hr class="divider divider-lv1" />
<form action="" method="post" id="cohort-inline-discussions-form" class="cohort-inline-discussions-form">
<div class="wrapper cohort-management-supplemental">
<div class="wrapper discussions-management-supplemental">
<div class="form-fields">
<div class="form-field">
<div class="inline-discussion-topics">
<h4 class="hd hd-4 subsection-title"><%- gettext('Content-Specific Discussion Topics') %></h4>
<p><%- gettext('Specify whether content-specific discussion topics are divided by cohort.') %></p>
<div class="always_cohort_inline_discussions">
<div class="always_divide_inline_discussions">
<label>
<input type="radio" name="inline" class="check-all-inline-discussions" <%- alwaysCohortInlineDiscussions ? 'checked="checked"' : '' %>/>
<input type="radio" name="inline" class="check-all-inline-discussions" <%- alwaysDivideInlineDiscussions ? 'checked="checked"' : '' %>/>
<span class="all-inline-discussions"><%- gettext('Always cohort content-specific discussion topics') %></span>
</label>
</div>
<div class="cohort_inline_discussions">
<div class="divide_inline_discussions">
<label>
<input type="radio" name="inline" class="check-cohort-inline-discussions" <%- alwaysCohortInlineDiscussions ? '' : 'checked="checked"' %>/>
<input type="radio" name="inline" class="check-cohort-inline-discussions" <%- alwaysDivideInlineDiscussions ? '' : 'checked="checked"' %>/>
<span class="all-inline-discussions"><%- gettext('Cohort selected content-specific discussion topics') %></span>
</label>
</div>

View File

@@ -81,7 +81,7 @@ from openedx.core.djangolib.markup import HTML
## Include Underscore templates
<%block name="header_extras">
% for template_name in ["cohorts", "enrollment-code-lookup-links", "cohort-editor", "cohort-group-header", "cohort-selector", "cohort-form", "notification", "cohort-state", "cohort-discussions-inline", "cohort-discussions-course-wide", "cohort-discussions-category", "cohort-discussions-subcategory", "certificate-white-list", "certificate-white-list-editor", "certificate-bulk-white-list", "certificate-invalidation"]:
% for template_name in ["cohorts", "discussions", "enrollment-code-lookup-links", "cohort-editor", "cohort-group-header", "cohort-selector", "cohort-form", "notification", "cohort-state", "divided-discussions-inline", "divided-discussions-course-wide", "cohort-discussions-category", "cohort-discussions-subcategory", "certificate-white-list", "certificate-white-list-editor", "certificate-bulk-white-list", "certificate-invalidation"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="instructor/instructor_dashboard_2/${template_name}.underscore" />
</script>

View File

@@ -504,6 +504,15 @@ urlpatterns += (
include(COURSE_URLS)
),
# Discussions Management
url(
r'^courses/{}/discussions/settings$'.format(
settings.COURSE_KEY_PATTERN,
),
'openedx.core.djangoapps.course_groups.views.course_discussions_settings_handler',
name='course_discussions_settings',
),
# Cohorts management
url(
r'^courses/{}/cohorts/settings$'.format(
@@ -548,11 +557,11 @@ urlpatterns += (
name='debug_cohort_mgmt',
),
url(
r'^courses/{}/cohorts/topics$'.format(
r'^courses/{}/discussion/topics$'.format(
settings.COURSE_KEY_PATTERN,
),
'openedx.core.djangoapps.course_groups.views.cohort_discussion_topics',
name='cohort_discussion_topics',
'openedx.core.djangoapps.course_groups.views.discussion_topics',
name='discussion_topics',
),
url(
r'^courses/{}/verified_track_content/settings'.format(

View File

@@ -121,6 +121,16 @@ def is_course_cohorted(course_key):
return _get_course_cohort_settings(course_key).is_cohorted
def get_course_cohort_id(course_key):
"""
Given a course key, return the int id for the cohort settings.
Raises:
Http404 if the course doesn't exist.
"""
return _get_course_cohort_settings(course_key).id
def set_course_cohorted(course_key, cohorted):
"""
Given a course course and a boolean, sets whether or not the course is cohorted.

View File

@@ -135,43 +135,72 @@ def config_course_cohorts_legacy(
pass
# pylint: disable=dangerous-default-value
def config_course_discussions(
course,
discussion_topics={},
divided_discussions=[],
always_divide_inline_discussions=False
):
"""
Set discussions and configure divided discussions for a course.
Arguments:
course: CourseDescriptor
discussion_topics (Dict): Discussion topic names. Picks ids and
sort_keys automatically.
divided_discussions: Discussion topics to divide. Converts the
list to use the same ids as discussion topic names.
always_divide_inline_discussions (bool): Whether inline discussions
should be divided by default.
Returns:
Nothing -- modifies course in place.
"""
def to_id(name):
"""Convert name to id."""
return topic_name_to_id(course, name)
set_course_discussion_settings(
course.id,
divided_discussions=[to_id(name) for name in divided_discussions],
always_divide_inline_discussions=always_divide_inline_discussions,
division_scheme=CourseDiscussionSettings.COHORT,
)
course.discussion_topics = dict((name, {"sort_key": "A", "id": to_id(name)})
for name in discussion_topics)
try:
# Not implemented for XMLModulestore, which is used by test_cohorts.
modulestore().update_item(course, ModuleStoreEnum.UserID.test)
except NotImplementedError:
pass
# pylint: disable=dangerous-default-value
def config_course_cohorts(
course,
is_cohorted,
auto_cohorts=[],
manual_cohorts=[],
discussion_topics=[],
divided_discussions=[],
always_divide_inline_discussions=False
):
"""
Set discussions and configure cohorts for a course.
Set and configure cohorts for a course.
Arguments:
course: CourseDescriptor
is_cohorted (bool): Is the course cohorted?
auto_cohorts (list): Names of auto cohorts to create.
manual_cohorts (list): Names of manual cohorts to create.
discussion_topics (list): Discussion topic names. Picks ids and
sort_keys automatically.
divided_discussions: Discussion topics to divide. Converts the
list to use the same ids as discussion topic names.
always_divide_inline_discussions (bool): Whether inline discussions
should be divided by default.
Returns:
Nothing -- modifies course in place.
"""
def to_id(name):
"""Convert name to id."""
return topic_name_to_id(course, name)
set_course_cohorted(course.id, is_cohorted)
set_course_discussion_settings(
course.id,
divided_discussions=[to_id(name) for name in divided_discussions],
always_divide_inline_discussions=always_divide_inline_discussions,
division_scheme=CourseDiscussionSettings.COHORT,
)
@@ -183,8 +212,6 @@ def config_course_cohorts(
cohort = CohortFactory(course_id=course.id, name=cohort_name)
CourseCohortFactory(course_user_group=cohort, assignment_type=CourseCohort.MANUAL)
course.discussion_topics = dict((name, {"sort_key": "A", "id": to_id(name)})
for name in discussion_topics)
try:
# Not implemented for XMLModulestore, which is used by test_cohorts.
modulestore().update_item(course, ModuleStoreEnum.UserID.test)

View File

@@ -25,15 +25,17 @@ from openedx.core.djangolib.testing.utils import skip_unless_lms
from ..models import CourseUserGroup, CourseCohort
from ..views import (
course_cohort_settings_handler, cohort_handler, users_in_cohort, add_users_to_cohort, remove_user_from_cohort,
link_cohort_to_partition_group, cohort_discussion_topics
course_cohort_settings_handler, course_discussions_settings_handler,
cohort_handler, users_in_cohort,
add_users_to_cohort, remove_user_from_cohort,
link_cohort_to_partition_group, discussion_topics
)
from ..cohorts import (
get_cohort, get_cohort_by_name, get_cohort_by_id,
DEFAULT_COHORT_NAME, get_group_info_for_cohort
)
from .helpers import (
config_course_cohorts, config_course_cohorts_legacy, CohortFactory, CourseCohortFactory, topic_name_to_id
config_course_cohorts, config_course_discussions, config_course_cohorts_legacy, CohortFactory, CourseCohortFactory, topic_name_to_id
)
@@ -99,13 +101,13 @@ class CohortViewsTestCase(ModuleStoreTestCase):
view_args.insert(0, request)
self.assertRaises(Http404, view, *view_args)
def create_cohorted_discussions(self):
def create_divided_discussions(self):
"""
Set up a cohorted discussion in the system, complete with all the fixings
Set up a divided discussion in the system, complete with all the fixings
"""
cohorted_inline_discussions = ['Topic A']
cohorted_course_wide_discussions = ["Topic B"]
cohorted_discussions = cohorted_inline_discussions + cohorted_course_wide_discussions
divided_inline_discussions = ['Topic A']
divided_course_wide_discussions = ["Topic B"]
divided_discussions = divided_inline_discussions + divided_course_wide_discussions
# inline discussion
ItemFactory.create(
@@ -124,10 +126,14 @@ class CohortViewsTestCase(ModuleStoreTestCase):
config_course_cohorts(
self.course,
is_cohorted=True,
discussion_topics=discussion_topics,
divided_discussions=cohorted_discussions
)
return cohorted_inline_discussions, cohorted_course_wide_discussions
config_course_discussions(
self.course,
discussion_topics=discussion_topics,
divided_discussions=divided_discussions
)
return divided_inline_discussions, divided_course_wide_discussions
def get_handler(self, course, cohort=None, expected_response_code=200, handler=cohort_handler):
"""
@@ -177,6 +183,129 @@ class CohortViewsTestCase(ModuleStoreTestCase):
return json.loads(response.content)
@attr(shard=2)
class CourseDiscussionsHandlerTestCase(CohortViewsTestCase):
"""
Tests the course_discussion_settings_handler
"""
def get_expected_response(self):
"""
Returns the static response dict.
"""
return {
u'always_divide_inline_discussions': False,
u'divided_inline_discussions': [],
u'divided_course_wide_discussions': [],
u'id': 1
}
def test_non_staff(self):
"""
Verify that we cannot access course_discussions_settings_handler if we're a non-staff user.
"""
self._verify_non_staff_cannot_access(course_discussions_settings_handler, "GET", [unicode(self.course.id)])
self._verify_non_staff_cannot_access(course_discussions_settings_handler, "PATCH", [unicode(self.course.id)])
def test_update_always_divide_inline_discussion_settings(self):
"""
Verify that course_discussions_settings_handler is working for always_divide_inline_discussions via HTTP PATCH.
"""
config_course_cohorts(self.course, is_cohorted=True)
response = self.get_handler(self.course, handler=course_discussions_settings_handler)
expected_response = self.get_expected_response()
self.assertEqual(response, expected_response)
expected_response['always_divide_inline_discussions'] = True
response = self.patch_handler(self.course, data=expected_response, handler=course_discussions_settings_handler)
self.assertEqual(response, expected_response)
def test_update_course_wide_discussion_settings(self):
"""
Verify that course_discussions_settings_handler is working for divided_course_wide_discussions via HTTP PATCH.
"""
# course-wide discussion
discussion_topics = {
"Topic B": {"id": "Topic B"},
}
config_course_cohorts(self.course, is_cohorted=True)
config_course_discussions(self.course, discussion_topics=discussion_topics)
response = self.get_handler(self.course, handler=course_discussions_settings_handler)
expected_response = self.get_expected_response()
self.assertEqual(response, expected_response)
expected_response['divided_course_wide_discussions'] = [topic_name_to_id(self.course, "Topic B")]
response = self.patch_handler(self.course, data=expected_response, handler=course_discussions_settings_handler)
self.assertEqual(response, expected_response)
def test_update_inline_discussion_settings(self):
"""
Verify that course_discussions_settings_handler is working for divided_inline_discussions via HTTP PATCH.
"""
config_course_cohorts(self.course, is_cohorted=True)
response = self.get_handler(self.course, handler=course_discussions_settings_handler)
expected_response = self.get_expected_response()
self.assertEqual(response, expected_response)
now = datetime.now()
# inline discussion
ItemFactory.create(
parent_location=self.course.location,
category="discussion",
discussion_id="Topic_A",
discussion_category="Chapter",
discussion_target="Discussion",
start=now
)
expected_response['divided_inline_discussions'] = ["Topic_A"]
response = self.patch_handler(self.course, data=expected_response, handler=course_discussions_settings_handler)
self.assertEqual(response, expected_response)
def test_get_settings(self):
"""
Verify that course_discussions_settings_handler is working for HTTP GET.
"""
divided_inline_discussions, divided_course_wide_discussions = self.create_divided_discussions()
response = self.get_handler(self.course, handler=course_discussions_settings_handler)
expected_response = self.get_expected_response()
expected_response['divided_inline_discussions'] = [topic_name_to_id(self.course, name)
for name in divided_inline_discussions]
expected_response['divided_course_wide_discussions'] = [topic_name_to_id(self.course, name)
for name in divided_course_wide_discussions]
self.assertEqual(response, expected_response)
def test_update_settings_with_invalid_field_data_type(self):
"""
Verify that course_discussions_settings_handler return HTTP 400 if field data type is incorrect.
"""
config_course_cohorts(self.course, is_cohorted=True)
response = self.patch_handler(
self.course,
data={'always_divide_inline_discussions': ''},
expected_response_code=400,
handler=course_discussions_settings_handler
)
self.assertEqual(
"Incorrect field type for `{}`. Type must be `{}`".format('always_divide_inline_discussions', bool.__name__),
response.get("error")
)
@attr(shard=2)
class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
"""
@@ -189,9 +318,6 @@ class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
"""
return {
'is_cohorted': True,
'always_cohort_inline_discussions': False,
'cohorted_inline_discussions': [],
'cohorted_course_wide_discussions': [],
'id': 1
}
@@ -202,22 +328,6 @@ class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
self._verify_non_staff_cannot_access(course_cohort_settings_handler, "GET", [unicode(self.course.id)])
self._verify_non_staff_cannot_access(course_cohort_settings_handler, "PATCH", [unicode(self.course.id)])
def test_get_settings(self):
"""
Verify that course_cohort_settings_handler is working for HTTP GET.
"""
cohorted_inline_discussions, cohorted_course_wide_discussions = self.create_cohorted_discussions()
response = self.get_handler(self.course, handler=course_cohort_settings_handler)
expected_response = self.get_expected_response()
expected_response['cohorted_inline_discussions'] = [topic_name_to_id(self.course, name)
for name in cohorted_inline_discussions]
expected_response['cohorted_course_wide_discussions'] = [topic_name_to_id(self.course, name)
for name in cohorted_course_wide_discussions]
self.assertEqual(response, expected_response)
def test_update_is_cohorted_settings(self):
"""
Verify that course_cohort_settings_handler is working for is_cohorted via HTTP PATCH.
@@ -235,71 +345,6 @@ class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
self.assertEqual(response, expected_response)
def test_update_always_cohort_inline_discussion_settings(self):
"""
Verify that course_cohort_settings_handler is working for always_cohort_inline_discussions via HTTP PATCH.
"""
config_course_cohorts(self.course, is_cohorted=True)
response = self.get_handler(self.course, handler=course_cohort_settings_handler)
expected_response = self.get_expected_response()
self.assertEqual(response, expected_response)
expected_response['always_cohort_inline_discussions'] = True
response = self.patch_handler(self.course, data=expected_response, handler=course_cohort_settings_handler)
self.assertEqual(response, expected_response)
def test_update_course_wide_discussion_settings(self):
"""
Verify that course_cohort_settings_handler is working for cohorted_course_wide_discussions via HTTP PATCH.
"""
# course-wide discussion
discussion_topics = {
"Topic B": {"id": "Topic B"},
}
config_course_cohorts(self.course, is_cohorted=True, discussion_topics=discussion_topics)
response = self.get_handler(self.course, handler=course_cohort_settings_handler)
expected_response = self.get_expected_response()
self.assertEqual(response, expected_response)
expected_response['cohorted_course_wide_discussions'] = [topic_name_to_id(self.course, "Topic B")]
response = self.patch_handler(self.course, data=expected_response, handler=course_cohort_settings_handler)
self.assertEqual(response, expected_response)
def test_update_inline_discussion_settings(self):
"""
Verify that course_cohort_settings_handler is working for cohorted_inline_discussions via HTTP PATCH.
"""
config_course_cohorts(self.course, is_cohorted=True)
response = self.get_handler(self.course, handler=course_cohort_settings_handler)
expected_response = self.get_expected_response()
self.assertEqual(response, expected_response)
now = datetime.now()
# inline discussion
ItemFactory.create(
parent_location=self.course.location,
category="discussion",
discussion_id="Topic_A",
discussion_category="Chapter",
discussion_target="Discussion",
start=now
)
expected_response['cohorted_inline_discussions'] = ["Topic_A"]
response = self.patch_handler(self.course, data=expected_response, handler=course_cohort_settings_handler)
self.assertEqual(response, expected_response)
def test_update_settings_with_missing_field(self):
"""
Verify that course_cohort_settings_handler return HTTP 400 if required data field is missing from post data.
@@ -315,17 +360,6 @@ class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
"""
config_course_cohorts(self.course, is_cohorted=True)
response = self.patch_handler(
self.course,
data={'always_cohort_inline_discussions': ''},
expected_response_code=400,
handler=course_cohort_settings_handler
)
self.assertEqual(
"Incorrect field type for `{}`. Type must be `{}`".format('always_divide_inline_discussions', bool.__name__),
response.get("error")
)
response = self.patch_handler(
self.course,
data={'is_cohorted': ''},
@@ -1240,25 +1274,25 @@ class RemoveUserFromCohortTestCase(CohortViewsTestCase):
@attr(shard=2)
@skip_unless_lms
class CourseCohortDiscussionTopicsTestCase(CohortViewsTestCase):
class CourseDividedDiscussionTopicsTestCase(CohortViewsTestCase):
"""
Tests the `cohort_discussion_topics` view.
Tests the `divide_discussion_topics` view.
"""
def test_non_staff(self):
"""
Verify that we cannot access cohort_discussion_topics if we're a non-staff user.
Verify that we cannot access divide_discussion_topics if we're a non-staff user.
"""
self._verify_non_staff_cannot_access(cohort_discussion_topics, "GET", [unicode(self.course.id)])
self._verify_non_staff_cannot_access(discussion_topics, "GET", [unicode(self.course.id)])
def test_get_discussion_topics(self):
"""
Verify that course_cohort_settings_handler is working for HTTP GET.
Verify that divide_discussion_topics is working for HTTP GET.
"""
# create inline & course-wide discussion to verify the different map.
self.create_cohorted_discussions()
self.create_divided_discussions()
response = self.get_handler(self.course, handler=cohort_discussion_topics)
response = self.get_handler(self.course, handler=discussion_topics)
start_date = response['inline_discussions']['subcategories']['Chapter']['start_date']
expected_response = {
"course_wide_discussions": {

View File

@@ -64,20 +64,29 @@ def unlink_cohort_partition_group(cohort):
# pylint: disable=invalid-name
def _get_course_cohort_settings_representation(course, is_cohorted, course_discussion_settings):
def _get_course_cohort_settings_representation(cohort_id, is_cohorted):
"""
Returns a JSON representation of a course cohort settings.
"""
return {
'id': cohort_id,
'is_cohorted': is_cohorted,
}
def _get_course_discussion_settings_representation(course, course_discussion_settings):
"""
Returns a JSON representation of a course discussion settings.
"""
divided_course_wide_discussions, divided_inline_discussions = get_divided_discussions(
course, course_discussion_settings
)
return {
'id': course_discussion_settings.id,
'is_cohorted': is_cohorted,
'cohorted_inline_discussions': divided_inline_discussions,
'cohorted_course_wide_discussions': divided_course_wide_discussions,
'always_cohort_inline_discussions': course_discussion_settings.always_divide_inline_discussions,
'divided_inline_discussions': divided_inline_discussions,
'divided_course_wide_discussions': divided_course_wide_discussions,
'always_divide_inline_discussions': course_discussion_settings.always_divide_inline_discussions,
}
@@ -117,6 +126,61 @@ def get_divided_discussions(course, discussion_settings):
return divided_course_wide_discussions, divided_inline_discussions
@require_http_methods(("GET", "PATCH"))
@ensure_csrf_cookie
@expect_json
@login_required
def course_discussions_settings_handler(request, course_key_string):
"""
The restful handler for divided discussion setting requests. Requires JSON.
This will raise 404 if user is not staff.
GET
Returns the JSON representation of divided discussion settings for the course.
PATCH
Updates the divided discussion settings for the course. Returns the JSON representation of updated settings.
"""
course_key = CourseKey.from_string(course_key_string)
course = get_course_with_access(request.user, 'staff', course_key)
discussion_settings = get_course_discussion_settings(course_key)
if request.method == 'PATCH':
divided_course_wide_discussions, divided_inline_discussions = get_divided_discussions(
course, discussion_settings
)
settings_to_change = {}
if 'divided_course_wide_discussions' in request.json or 'divided_inline_discussions' in request.json:
divided_course_wide_discussions = request.json.get(
'divided_course_wide_discussions', divided_course_wide_discussions
)
divided_inline_discussions = request.json.get(
'divided_inline_discussions', divided_inline_discussions
)
settings_to_change['divided_discussions'] = divided_course_wide_discussions + divided_inline_discussions
if 'always_divide_inline_discussions' in request.json:
settings_to_change['always_divide_inline_discussions'] = request.json.get(
'always_divide_inline_discussions'
)
if not settings_to_change:
return JsonResponse({"error": unicode("Bad Request")}, 400)
try:
if settings_to_change:
discussion_settings = set_course_discussion_settings(course_key, **settings_to_change)
except ValueError as err:
# Note: error message not translated because it is not exposed to the user (UI prevents this state).
return JsonResponse({"error": unicode(err)}, 400)
return JsonResponse(_get_course_discussion_settings_representation(
course,
discussion_settings
))
@require_http_methods(("GET", "PATCH"))
@ensure_csrf_cookie
@expect_json
@@ -131,51 +195,27 @@ def course_cohort_settings_handler(request, course_key_string):
Updates the cohort settings for the course. Returns the JSON representation of updated settings.
"""
course_key = CourseKey.from_string(course_key_string)
# Although this course data is not used this method will return 404 is user is not staff
course = get_course_with_access(request.user, 'staff', course_key)
is_cohorted = cohorts.is_course_cohorted(course_key)
discussion_settings = get_course_discussion_settings(course_key)
if request.method == 'PATCH':
divided_course_wide_discussions, divided_inline_discussions = get_divided_discussions(
course, discussion_settings
)
settings_to_change = {}
if 'is_cohorted' in request.json:
settings_to_change['is_cohorted'] = request.json.get('is_cohorted')
if 'cohorted_course_wide_discussions' in request.json or 'cohorted_inline_discussions' in request.json:
divided_course_wide_discussions = request.json.get(
'cohorted_course_wide_discussions', divided_course_wide_discussions
)
divided_inline_discussions = request.json.get(
'cohorted_inline_discussions', divided_inline_discussions
)
settings_to_change['divided_discussions'] = divided_course_wide_discussions + divided_inline_discussions
if 'always_cohort_inline_discussions' in request.json:
settings_to_change['always_divide_inline_discussions'] = request.json.get(
'always_cohort_inline_discussions'
)
if not settings_to_change:
if 'is_cohorted' not in request.json:
return JsonResponse({"error": unicode("Bad Request")}, 400)
is_cohorted = request.json.get('is_cohorted')
try:
if 'is_cohorted' in settings_to_change:
is_cohorted = settings_to_change['is_cohorted']
cohorts.set_course_cohorted(course_key, is_cohorted)
del settings_to_change['is_cohorted']
settings_to_change['division_scheme'] = CourseDiscussionSettings.COHORT if is_cohorted \
else CourseDiscussionSettings.NONE
if settings_to_change:
discussion_settings = set_course_discussion_settings(course_key, **settings_to_change)
cohorts.set_course_cohorted(course_key, is_cohorted)
scheme = CourseDiscussionSettings.COHORT if is_cohorted else CourseDiscussionSettings.NONE
scheme_settings = {'division_scheme': scheme}
set_course_discussion_settings(course_key, **scheme_settings)
except ValueError as err:
# Note: error message not translated because it is not exposed to the user (UI prevents this state).
return JsonResponse({"error": unicode(err)}, 400)
return JsonResponse(_get_course_cohort_settings_representation(course, is_cohorted, discussion_settings))
return JsonResponse(_get_course_cohort_settings_representation(
cohorts.get_course_cohort_id(course_key),
cohorts.is_course_cohorted(course_key)
))
@require_http_methods(("GET", "PUT", "POST", "PATCH"))
@@ -428,9 +468,9 @@ def debug_cohort_mgmt(request, course_key_string):
@expect_json
@login_required
def cohort_discussion_topics(request, course_key_string):
def discussion_topics(request, course_key_string):
"""
The handler for cohort discussion categories requests.
The handler for divided discussion categories requests.
This will raise 404 if user is not staff.
Returns the JSON representation of discussion topics w.r.t categories for the course.