Commit c23c0b99 by Albert St. Aubin Committed by cahrens

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

parent 8951ac8c
...@@ -47,6 +47,15 @@ class InstructorDashboardPage(CoursePage): ...@@ -47,6 +47,15 @@ class InstructorDashboardPage(CoursePage):
cohort_management_section.wait_for_page() cohort_management_section.wait_for_page()
return cohort_management_section 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): def select_data_download(self):
""" """
Selects the data download tab and returns a DataDownloadPage. Selects the data download tab and returns a DataDownloadPage.
...@@ -666,54 +675,63 @@ class CohortManagementSection(PageObject): ...@@ -666,54 +675,63 @@ class CohortManagementSection(PageObject):
self.q(css=self._bounded_selector('.cohorts-state')).first.click() self.q(css=self._bounded_selector('.cohorts-state')).first.click()
self.wait_for_ajax() 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() return (self.q(css=self._bounded_selector('.cohort-management-nav')).visible and
self.wait_for_element_visibility("#cohort-discussions-management", "Waiting for discussions to appear") 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 class DiscussionManagementSection(PageObject):
self.q(css=self._bounded_selector('.cohort-inline-discussions-nav')).visible)
def select_discussion_topic(self, key): url = None
discussion_form_selectors = {
'course-wide': '.cohort-course-wide-discussions-form',
'inline': '.cohort-inline-discussions-form'
}
def is_browser_on_page(self):
return self.q(css=self.discussion_form_selectors['course-wide']).present
def _bounded_selector(self, selector):
""" """
Selects discussion topic checkbox by clicking on it. Return `selector`, but limited to the divided discussion management context.
""" """
self.q(css=self._bounded_selector(".check-discussion-subcategory-%s" % key)).first.click() return '.discussions-management {}'.format(selector)
def select_always_inline_discussion(self): def is_save_button_disabled(self, key):
""" """
Selects the always_cohort_inline_discussions radio button. Returns the status for form's save button, enabled or disabled.
""" """
self.q(css=self._bounded_selector(".check-all-inline-discussions")).first.click() save_button_css = '%s %s' % (self.discussion_form_selectors[key], '.action-save')
disabled = self.q(css=self._bounded_selector(save_button_css)).attrs('disabled')
return disabled[0] == 'true'
def always_inline_discussion_selected(self): def discussion_topics_visible(self):
""" """
Returns true if always_cohort_inline_discussions radio button is selected. Returns the visibility status of divide discussion controls.
""" """
return len(self.q(css=self._bounded_selector(".check-all-inline-discussions:checked"))) > 0 return (self.q(css=self._bounded_selector('.course-wide-discussions-nav')).visible and
self.q(css=self._bounded_selector('.inline-discussions-nav')).visible)
def cohort_some_inline_discussion_selected(self): def divided_discussion_heading_is_visible(self, key):
""" """
Returns true if some_cohort_inline_discussions radio button is selected. Returns the text of discussion topic headings if it exists, otherwise return False.
""" """
return len(self.q(css=self._bounded_selector(".check-cohort-inline-discussions:checked"))) > 0 form_heading_css = '%s %s' % (self.discussion_form_selectors[key], '.subsection-title')
discussion_heading = self.q(css=self._bounded_selector(form_heading_css))
def select_cohort_some_inline_discussion(self): if len(discussion_heading) == 0:
return False
return discussion_heading.first.text[0]
def select_always_inline_discussion(self):
""" """
Selects the cohort_some_inline_discussions radio button. Selects the always_divide_inline_discussions radio button.
""" """
self.q(css=self._bounded_selector(".check-cohort-inline-discussions")).first.click() self.q(css=self._bounded_selector(".check-all-inline-discussions")).first.click()
def inline_discussion_topics_disabled(self): def inline_discussion_topics_disabled(self):
""" """
...@@ -722,35 +740,45 @@ class CohortManagementSection(PageObject): ...@@ -722,35 +740,45 @@ class CohortManagementSection(PageObject):
inline_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-inline')) 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 all(topic.get_attribute('disabled') == 'true' for topic in inline_topics)
def is_save_button_disabled(self, key): def save_discussion_topics(self, key):
""" """
Returns the status for form's save button, enabled or disabled. Saves the discussion topics.
""" """
save_button_css = '%s %s' % (self.discussion_form_selectors[key], '.action-save') save_button_css = '%s %s' % (self.discussion_form_selectors[key], '.action-save')
disabled = self.q(css=self._bounded_selector(save_button_css)).attrs('disabled') self.q(css=self._bounded_selector(save_button_css)).first.click()
return disabled[0] == 'true'
def is_category_selected(self): def always_inline_discussion_selected(self):
""" """
Returns the status for category checkboxes. Returns true if always_divide_inline_discussions radio button is selected.
""" """
return self.q(css=self._bounded_selector('.check-discussion-category:checked')).is_present() return len(self.q(css=self._bounded_selector(".check-all-inline-discussions:checked"))) > 0
def get_cohorted_topics_count(self, key): def divide_some_inline_discussion_selected(self):
""" """
Returns the count for cohorted topics. Returns true if divide_some_inline_discussions radio button is selected.
""" """
cohorted_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-%s:checked' % key)) return len(self.q(css=self._bounded_selector(".check-cohort-inline-discussions:checked"))) > 0
return len(cohorted_topics.results)
def save_discussion_topics(self, key): def select_divide_some_inline_discussion(self):
""" """
Saves the discussion topics. Selects the divide_some_inline_discussions radio button.
""" """
save_button_css = '%s %s' % (self.discussion_form_selectors[key], '.action-save') self.q(css=self._bounded_selector(".check-cohort-inline-discussions")).first.click()
self.q(css=self._bounded_selector(save_button_css)).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 get_cohort_discussions_message(self, key, msg_type="confirmation"): 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. Returns the message related to modifying discussion topics.
""" """
...@@ -767,23 +795,11 @@ class CohortManagementSection(PageObject): ...@@ -767,23 +795,11 @@ class CohortManagementSection(PageObject):
return '' return ''
return message_title.first.text[0] 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.
"""
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). Returns the status for category checkboxes.
""" """
return (self.q(css=self._bounded_selector('.cohort-management-nav')).visible and return self.q(css=self._bounded_selector('.check-discussion-category:checked')).is_present()
self.q(css=self._bounded_selector('.wrapper-cohort-supplemental')).visible)
class MembershipPageAutoEnrollSection(PageObject): class MembershipPageAutoEnrollSection(PageObject):
......
...@@ -76,11 +76,16 @@ class CohortTestMixin(object): ...@@ -76,11 +76,16 @@ class CohortTestMixin(object):
def enable_cohorting(self, course_fixture): 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 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) 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): def disable_cohorting(self, course_fixture):
""" """
......
...@@ -694,267 +694,6 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin ...@@ -694,267 +694,6 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin
@attr(shard=6) @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): class CohortContentGroupAssociationTest(UniqueCourseTest, CohortTestMixin):
""" """
Tests for linking between content groups and cohort in the instructor dashboard. Tests for linking between content groups and cohort in the instructor dashboard.
......
# -*- 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())
...@@ -26,7 +26,7 @@ from courseware.tests.factories import InstructorFactory ...@@ -26,7 +26,7 @@ from courseware.tests.factories import InstructorFactory
from courseware.tabs import get_course_tab_list from courseware.tabs import get_course_tab_list
from openedx.core.djangoapps.course_groups import cohorts 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.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 student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentFactory
from openedx.core.djangoapps.content.course_structures.models import CourseStructure from openedx.core.djangoapps.content.course_structures.models import CourseStructure
from openedx.core.djangoapps.util.testing import ContentGroupTestCase from openedx.core.djangoapps.util.testing import ContentGroupTestCase
...@@ -478,7 +478,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase): ...@@ -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") self.create_discussion("Chapter", "Discussion")
set_discussion_division_settings(self.course.id, enable_cohorts=True, always_divide_inline_discussions=True) set_discussion_division_settings(self.course.id, enable_cohorts=True, always_divide_inline_discussions=True)
...@@ -502,7 +502,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase): ...@@ -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") self.create_discussion("Chapter", "Discussion")
set_discussion_division_settings(self.course.id, enable_cohorts=True) set_discussion_division_settings(self.course.id, enable_cohorts=True)
...@@ -1301,15 +1301,16 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase): ...@@ -1301,15 +1301,16 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
) )
# not cohorted # 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( self.assertFalse(
utils.is_commentable_divided(course.id, to_id("General")), utils.is_commentable_divided(course.id, to_id("General")),
"Course isn't cohorted" "Course isn't cohorted"
) )
# cohorted, but top level topics aren't # 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.assertTrue(cohorts.is_course_cohorted(course.id))
self.assertFalse( self.assertFalse(
...@@ -1320,10 +1321,9 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase): ...@@ -1320,10 +1321,9 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
# cohorted, including "Feedback" top-level topics aren't # cohorted, including "Feedback" top-level topics aren't
config_course_cohorts( config_course_cohorts(
course, course,
is_cohorted=True, is_cohorted=True
discussion_topics=["General", "Feedback"],
divided_discussions=["Feedback"]
) )
config_course_discussions(course, discussion_topics=["General", "Feedback"], divided_discussions=["Feedback"])
self.assertTrue(cohorts.is_course_cohorted(course.id)) self.assertTrue(cohorts.is_course_cohorted(course.id))
self.assertFalse( self.assertFalse(
...@@ -1345,42 +1345,52 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase): ...@@ -1345,42 +1345,52 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
config_course_cohorts( config_course_cohorts(
course, course,
is_cohorted=True, is_cohorted=True,
)
config_course_discussions(
course,
discussion_topics=["General", "Feedback"], discussion_topics=["General", "Feedback"],
divided_discussions=["Feedback", "random_inline"] divided_discussions=["Feedback", "random_inline"]
) )
self.assertFalse( self.assertFalse(
utils.is_commentable_divided(course.id, to_id("random")), utils.is_commentable_divided(course.id, to_id("random")),
"By default, Non-top-level discussions are not cohorted in a cohorted courses." "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 # if always_divide_inline_discussions is set to False, non-top-level discussion are always
# non cohorted unless they are explicitly set in cohorted_discussions # not divided unless they are explicitly set in divided_discussions
config_course_cohorts( config_course_cohorts(
course, course,
is_cohorted=True, is_cohorted=True,
)
config_course_discussions(
course,
discussion_topics=["General", "Feedback"], discussion_topics=["General", "Feedback"],
divided_discussions=["Feedback", "random_inline"], divided_discussions=["Feedback", "random_inline"],
always_divide_inline_discussions=False always_divide_inline_discussions=False
) )
self.assertFalse( self.assertFalse(
utils.is_commentable_divided(course.id, to_id("random")), 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( self.assertTrue(
utils.is_commentable_divided(course.id, to_id("random_inline")), 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." "cohorted if explicitly set in cohorted_discussions."
) )
self.assertTrue( self.assertTrue(
utils.is_commentable_divided(course.id, to_id("Feedback")), 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): def test_is_commentable_divided_team(self):
course = modulestore().get_course(self.toy_course_key) course = modulestore().get_course(self.toy_course_key)
self.assertFalse(cohorts.is_course_cohorted(course.id)) 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) team = CourseTeamFactory(course_id=course.id)
# Verify that team discussions are not cohorted, but other discussions are # Verify that team discussions are not cohorted, but other discussions are
......
...@@ -125,6 +125,7 @@ def instructor_dashboard_2(request, course_id): ...@@ -125,6 +125,7 @@ def instructor_dashboard_2(request, course_id):
_section_course_info(course, access), _section_course_info(course, access),
_section_membership(course, access, is_white_label), _section_membership(course, access, is_white_label),
_section_cohort_management(course, access), _section_cohort_management(course, access),
_section_discussions_management(course, access),
_section_student_admin(course, access), _section_student_admin(course, access),
_section_data_download(course, access), _section_data_download(course, access),
] ]
...@@ -513,7 +514,6 @@ def _section_cohort_management(course, access): ...@@ -513,7 +514,6 @@ def _section_cohort_management(course, access):
), ),
'cohorts_url': reverse('cohorts', kwargs={'course_key_string': unicode(course_key)}), '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)}), '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_url': reverse(
'verified_track_cohorting', kwargs={'course_key_string': unicode(course_key)} 'verified_track_cohorting', kwargs={'course_key_string': unicode(course_key)}
), ),
...@@ -521,6 +521,21 @@ def _section_cohort_management(course, access): ...@@ -521,6 +521,21 @@ def _section_cohort_management(course, access):
return section_data 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): def _is_small_course(course_key):
""" Compares against MAX_ENROLLMENT_INSTR_BUTTONS to determine if course enrollment is considered small. """ """ Compares against MAX_ENROLLMENT_INSTR_BUTTONS to determine if course enrollment is considered small. """
is_small_course = False is_small_course = False
......
...@@ -1759,6 +1759,7 @@ REQUIRE_JS_PATH_OVERRIDES = { ...@@ -1759,6 +1759,7 @@ REQUIRE_JS_PATH_OVERRIDES = {
'js/student_profile/views/learner_profile_factory': 'js/student_profile/views/learner_profile_factory.js', '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/courseware/courseware_factory': 'js/courseware/courseware_factory.js',
'js/groups/views/cohorts_dashboard_factory': 'js/groups/views/cohorts_dashboard_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', 'draggabilly': 'js/vendor/draggabilly.js',
'hls': 'common/js/vendor/hls.js' 'hls': 'common/js/vendor/hls.js'
} }
......
(function(define) { (function(define) {
'use strict'; 'use strict';
define(['backbone'], function(Backbone) { define(['backbone'], function(Backbone) {
var DiscussionTopicsSettingsModel = Backbone.Model.extend({ var CourseDiscussionTopicDetailsModel = Backbone.Model.extend({
defaults: { defaults: {
course_wide_discussions: {}, course_wide_discussions: {},
inline_discussions: {} inline_discussions: {}
} }
}); });
return DiscussionTopicsSettingsModel; return CourseDiscussionTopicDetailsModel;
}); });
}).call(this, define || RequireJS.define); }).call(this, define || RequireJS.define);
(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);
(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);
(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);
(function(define) { (function(define) {
'use strict'; 'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/models/notification', 'js/views/notification'], define(['jquery', 'underscore', 'backbone', 'gettext', 'js/models/notification', 'js/views/notification'],
function($, _, Backbone) { function($, _, Backbone, gettext) {
var CohortDiscussionConfigurationView = Backbone.View.extend({ /* global NotificationModel, NotificationView */
var DividedDiscussionConfigurationView = Backbone.View.extend({
/** /**
* Add/Remove the disabled attribute on given element. * Add/Remove the disabled attribute on given element.
...@@ -14,53 +15,53 @@ ...@@ -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. * @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, var self = this,
cohortedDiscussions = []; dividedDiscussions = [];
_.each(self.$(selector), function(topic) { _.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. * It shows the error message(s) if any.
* @param {object} $element - Messages would be shown before this element. * @param {object} $element - Messages would be shown before this element.
* @param {object} fieldData - Data to update on the server. * @param {object} fieldData - Data to update on the server.
*/ */
saveForm: function($element, fieldData) { saveForm: function($element, fieldData) {
var self = this, var self = this,
cohortSettingsModel = this.cohortSettings, discussionSettingsModel = this.discussionSettings,
saveOperation = $.Deferred(), saveOperation = $.Deferred(),
showErrorMessage; showErrorMessage;
showErrorMessage = function(message) {
showErrorMessage = function(message, $element) {
self.showMessage(message, $element, 'error'); self.showMessage(message, $element, 'error');
}; };
this.removeNotification(); this.removeNotification();
cohortSettingsModel.save( discussionSettingsModel.save(
fieldData, {patch: true, wait: true} fieldData, {patch: true, wait: true}
).done(function() { ).done(function() {
saveOperation.resolve(); saveOperation.resolve();
}).fail(function(result) { }).fail(function(result) {
var errorMessage = null; var errorMessage = null,
jsonResponse;
try { try {
var jsonResponse = JSON.parse(result.responseText); jsonResponse = JSON.parse(result.responseText);
errorMessage = jsonResponse.error; errorMessage = jsonResponse.error;
} catch (e) { } catch (e) {
// Ignore the exception and show the default error message instead. // Ignore the exception and show the default error message instead.
} }
if (!errorMessage) { 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(); saveOperation.reject();
}); });
return saveOperation.promise(); return saveOperation.promise();
...@@ -92,6 +93,6 @@ ...@@ -92,6 +93,6 @@
} }
}); });
return CohortDiscussionConfigurationView; return DividedDiscussionConfigurationView;
}); });
}).call(this, define || RequireJS.define); }).call(this, define || RequireJS.define);
(function(define) { (function(define) {
'use strict'; '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'], 'edx-ui-toolkit/js/utils/html-utils'],
function($, _, Backbone, gettext, CohortDiscussionConfigurationView, HtmlUtils) { function($, _, Backbone, gettext, DividedDiscussionConfigurationView, HtmlUtils) {
var CourseWideDiscussionsView = CohortDiscussionConfigurationView.extend({ var CourseWideDiscussionsView = DividedDiscussionConfigurationView.extend({
events: { events: {
'change .check-discussion-subcategory-course-wide': 'discussionCategoryStateChanged', 'change .check-discussion-subcategory-course-wide': 'discussionCategoryStateChanged',
'click .cohort-course-wide-discussions-form .action-save': 'saveCourseWideDiscussionsForm' 'click .cohort-course-wide-discussions-form .action-save': 'saveCourseWideDiscussionsForm'
}, },
initialize: function(options) { initialize: function(options) {
this.template = HtmlUtils.template($('#cohort-discussions-course-wide-tpl').text()); this.template = HtmlUtils.template($('#divided-discussions-course-wide-tpl').text());
this.cohortSettings = options.cohortSettings; this.discussionSettings = options.discussionSettings;
}, },
render: function() { render: function() {
HtmlUtils.setHtml(this.$('.cohort-course-wide-discussions-nav'), this.template({ HtmlUtils.setHtml(this.$('.course-wide-discussions-nav'), this.template({
courseWideTopicsHtml: this.getCourseWideDiscussionsHtml( courseWideTopicsHtml: this.getCourseWideDiscussionsHtml(
this.model.get('course_wide_discussions') this.model.get('course_wide_discussions')
) )
...@@ -56,25 +56,27 @@ ...@@ -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) { saveCourseWideDiscussionsForm: function(event) {
event.preventDefault();
var self = this, var self = this,
courseWideCohortedDiscussions = self.getCohortedDiscussions( courseWideDividedDiscussions = self.getDividedDiscussions(
'.check-discussion-subcategory-course-wide:checked' '.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) self.saveForm(self.$('.course-wide-discussion-topics'), fieldData)
.done(function() { .done(function() {
self.model.fetch() self.model.fetch()
.done(function() { .done(function() {
self.render(); 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() { }).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'); self.showMessage(errorMessage, self.$('.course-wide-discussion-topics'), 'error');
}); });
}); });
......
(function(define) { (function(define) {
'use strict'; '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'], 'edx-ui-toolkit/js/utils/html-utils', 'js/vendor/jquery.qubit'],
function($, _, Backbone, gettext, CohortDiscussionConfigurationView, HtmlUtils) { function($, _, Backbone, gettext, DividedDiscussionConfigurationView, HtmlUtils) {
var InlineDiscussionsView = CohortDiscussionConfigurationView.extend({ var InlineDiscussionsView = DividedDiscussionConfigurationView.extend({
events: { events: {
'change .check-discussion-category': 'setSaveButton', 'change .check-discussion-category': 'setSaveButton',
'change .check-discussion-subcategory-inline': 'setSaveButton', 'change .check-discussion-subcategory-inline': 'setSaveButton',
...@@ -13,17 +13,19 @@ ...@@ -13,17 +13,19 @@
}, },
initialize: function(options) { initialize: function(options) {
this.template = HtmlUtils.template($('#cohort-discussions-inline-tpl').text()); this.template = HtmlUtils.template($('#divided-discussions-inline-tpl').text());
this.cohortSettings = options.cohortSettings; this.discussionSettings = options.discussionSettings;
}, },
render: function() { render: function() {
var alwaysCohortInlineDiscussions = this.cohortSettings.get('always_cohort_inline_discussions'), var inlineDiscussions = this.model.get('inline_discussions'),
inline_discussions = this.model.get('inline_discussions'); alwaysDivideInlineDiscussions = this.discussionSettings.get(
'always_divide_inline_discussions'
);
HtmlUtils.setHtml(this.$('.cohort-inline-discussions-nav'), this.template({ HtmlUtils.setHtml(this.$('.inline-discussions-nav'), this.template({
inlineDiscussionTopicsHtml: this.getInlineDiscussionsHtml(inline_discussions), inlineDiscussionTopicsHtml: this.getInlineDiscussionsHtml(inlineDiscussions),
alwaysCohortInlineDiscussions: alwaysCohortInlineDiscussions alwaysDivideInlineDiscussions: alwaysDivideInlineDiscussions
})); }));
// Provides the semantics for a nested list of tri-state checkboxes. // Provides the semantics for a nested list of tri-state checkboxes.
...@@ -32,7 +34,7 @@ ...@@ -32,7 +34,7 @@
// based on the checked values of any checkboxes in child elements of the DOM. // based on the checked values of any checkboxes in child elements of the DOM.
this.$('ul.inline-topics').qubit(); this.$('ul.inline-topics').qubit();
this.setElementsEnabled(alwaysCohortInlineDiscussions, true); this.setElementsEnabled(alwaysDivideInlineDiscussions, true);
}, },
/** /**
...@@ -99,45 +101,48 @@ ...@@ -99,45 +101,48 @@
* *
* Enable/Disable the category and sub-category checkboxes. * Enable/Disable the category and sub-category checkboxes.
* Enable/Disable the save button. * Enable/Disable the save button.
* @param {bool} enable_checkboxes - The flag to enable/disable the checkboxes. * @param {bool} enableCheckboxes - The flag to enable/disable the checkboxes.
* @param {bool} enable_save_button - The flag to enable/disable the save button. * @param {bool} enableSaveButton - The flag to enable/disable the save button.
*/ */
setElementsEnabled: function(enable_checkboxes, enable_save_button) { setElementsEnabled: function(enableCheckboxes, enableSaveButton) {
this.setDisabled(this.$('.check-discussion-category'), enable_checkboxes); this.setDisabled(this.$('.check-discussion-category'), enableCheckboxes);
this.setDisabled(this.$('.check-discussion-subcategory-inline'), enable_checkboxes); this.setDisabled(this.$('.check-discussion-subcategory-inline'), enableCheckboxes);
this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), enable_save_button); this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), enableSaveButton);
}, },
/** /**
* Enables the save button for inline discussions. * Enables the save button for inline discussions.
*/ */
setSaveButton: function(event) { setSaveButton: function() {
this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), false); 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) { saveInlineDiscussionsForm: function(event) {
event.preventDefault();
var self = this, var self = this,
cohortedInlineDiscussions = self.getCohortedDiscussions( dividedInlineDiscussions = self.getDividedDiscussions(
'.check-discussion-subcategory-inline:checked' '.check-discussion-subcategory-inline:checked'
), ),
fieldData = { fieldData = {
cohorted_inline_discussions: cohortedInlineDiscussions, divided_inline_discussions: dividedInlineDiscussions,
always_cohort_inline_discussions: self.$('.check-all-inline-discussions').prop('checked') always_divide_inline_discussions: self.$(
'.check-all-inline-discussions'
).prop('checked')
}; };
event.preventDefault();
self.saveForm(self.$('.inline-discussion-topics'), fieldData) self.saveForm(self.$('.inline-discussion-topics'), fieldData)
.done(function() { .done(function() {
self.model.fetch() self.model.fetch()
.done(function() { .done(function() {
self.render(); 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() { }).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'); self.showMessage(errorMessage, self.$('.inline-discussion-topics'), 'error');
}); });
}); });
......
...@@ -4,10 +4,7 @@ ...@@ -4,10 +4,7 @@
var CourseCohortSettingsModel = Backbone.Model.extend({ var CourseCohortSettingsModel = Backbone.Model.extend({
idAttribute: 'id', idAttribute: 'id',
defaults: { defaults: {
is_cohorted: false, is_cohorted: false
cohorted_inline_discussions: [],
cohorted_course_wide_discussions: [],
always_cohort_inline_discussions: false
} }
}); });
return CourseCohortSettingsModel; return CourseCohortSettingsModel;
......
...@@ -4,13 +4,11 @@ ...@@ -4,13 +4,11 @@
'js/groups/models/verified_track_settings', 'js/groups/models/verified_track_settings',
'js/groups/views/cohort_editor', 'js/groups/views/cohort_form', 'js/groups/views/cohort_editor', 'js/groups/views/cohort_form',
'js/groups/views/course_cohort_settings_notification', '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', 'js/groups/views/verified_track_settings_notification',
'edx-ui-toolkit/js/utils/html-utils', 'edx-ui-toolkit/js/utils/html-utils',
'js/views/file_uploader', 'js/models/notification', 'js/views/notification', 'string_utils'], 'js/views/file_uploader', 'js/models/notification', 'js/views/notification', 'string_utils'],
function($, _, Backbone, gettext, CohortModel, VerifiedTrackSettingsModel, CohortEditorView, CohortFormView, function($, _, Backbone, gettext, CohortModel, VerifiedTrackSettingsModel, CohortEditorView, CohortFormView,
CourseCohortSettingsNotificationView, InlineDiscussionsView, CourseWideDiscussionsView, CourseCohortSettingsNotificationView, VerifiedTrackSettingsNotificationView, HtmlUtils) {
VerifiedTrackSettingsNotificationView, HtmlUtils) {
var hiddenClass = 'is-hidden', var hiddenClass = 'is-hidden',
disabledClass = 'is-disabled', disabledClass = 'is-disabled',
enableCohortsSelector = '.cohorts-state'; enableCohortsSelector = '.cohorts-state';
...@@ -25,7 +23,6 @@ ...@@ -25,7 +23,6 @@
'click .cohort-management-add-form .action-cancel': 'cancelAddCohortForm', 'click .cohort-management-add-form .action-cancel': 'cancelAddCohortForm',
'click .link-cross-reference': 'showSection', 'click .link-cross-reference': 'showSection',
'click .toggle-cohort-management-secondary': 'showCsvUpload', 'click .toggle-cohort-management-secondary': 'showCsvUpload',
'click .toggle-cohort-management-discussions': 'showDiscussionTopics'
}, },
initialize: function(options) { initialize: function(options) {
...@@ -306,27 +303,6 @@ ...@@ -306,27 +303,6 @@
}).render(); }).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) { getSectionCss: function(section) {
return ".instructor-nav .nav-item [data-section='" + section + "']"; return ".instructor-nav .nav-item [data-section='" + section + "']";
......
(function(define, undefined) { (function(define, undefined) {
'use strict'; 'use strict';
define(['jquery', 'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/course_cohort_settings', 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'], 'js/groups/models/content_group'],
function($, CohortsView, CohortCollection, CourseCohortSettingsModel, DiscussionTopicsSettingsModel, ContentGroupModel) { function($, CohortsView, CohortCollection, CourseCohortSettingsModel, ContentGroupModel) {
return function(contentGroups, studioGroupConfigurationsUrl) { return function(contentGroups, studioGroupConfigurationsUrl) {
var contentGroupModels = $.map(contentGroups, function(group) { var contentGroupModels = $.map(contentGroups, function(group) {
return new ContentGroupModel({ return new ContentGroupModel({
...@@ -14,35 +14,29 @@ ...@@ -14,35 +14,29 @@
var cohorts = new CohortCollection(), var cohorts = new CohortCollection(),
courseCohortSettings = new CourseCohortSettingsModel(), 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');
cohorts.url = cohortManagementElement.data('cohorts_url');
courseCohortSettings.url = cohortManagementElement.data('course_cohort_settings_url');
discussionTopicsSettings.url = cohortManagementElement.data('discussion-topics-url');
var cohortsView = new CohortsView({ var cohortsView = new CohortsView({
el: cohortManagementElement, el: $cohortManagementElement,
model: cohorts, model: cohorts,
contentGroups: contentGroupModels, contentGroups: contentGroupModels,
cohortSettings: courseCohortSettings, cohortSettings: courseCohortSettings,
context: { context: {
discussionTopicsSettingsModel: discussionTopicsSettings, uploadCohortsCsvUrl: $cohortManagementElement.data('upload_cohorts_csv_url'),
uploadCohortsCsvUrl: cohortManagementElement.data('upload_cohorts_csv_url'), verifiedTrackCohortingUrl: $cohortManagementElement.data('verified_track_cohorting_url'),
verifiedTrackCohortingUrl: cohortManagementElement.data('verified_track_cohorting_url'),
studioGroupConfigurationsUrl: studioGroupConfigurationsUrl, studioGroupConfigurationsUrl: studioGroupConfigurationsUrl,
isCcxEnabled: cohortManagementElement.data('is_ccx_enabled') isCcxEnabled: $cohortManagementElement.data('is_ccx_enabled')
} }
}); });
cohorts.fetch().done(function() { cohorts.fetch().done(function() {
courseCohortSettings.fetch().done(function() { courseCohortSettings.fetch().done(function() {
discussionTopicsSettings.fetch().done(function() {
cohortsView.render(); cohortsView.render();
}); });
}); });
});
}; };
}); });
}).call(this, define || RequireJS.define); }).call(this, define || RequireJS.define);
......
(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);
...@@ -188,6 +188,9 @@ such that the value can be defined later than this assignment (file load order). ...@@ -188,6 +188,9 @@ such that the value can be defined later than this assignment (file load order).
constructor: window.InstructorDashboard.sections.CohortManagement, constructor: window.InstructorDashboard.sections.CohortManagement,
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#cohort_management') $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, constructor: window.InstructorDashboard.sections.Certificates,
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#certificates') $element: idashContent.find('.' + CSS_IDASH_SECTION + '#certificates')
}, { }, {
......
...@@ -2,13 +2,10 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers ...@@ -2,13 +2,10 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
'common/js/spec_helpers/template_helpers', 'common/js/spec_helpers/template_helpers',
'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/content_group', '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/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/course_cohort_settings_notification'
'js/groups/views/cohort_discussions', 'js/groups/views/cohort_discussions_course_wide', ],
'js/groups/views/cohort_discussions_inline'
],
function(Backbone, $, AjaxHelpers, TemplateHelpers, CohortsView, CohortCollection, ContentGroupModel, function(Backbone, $, AjaxHelpers, TemplateHelpers, CohortsView, CohortCollection, ContentGroupModel,
CourseCohortSettingsModel, AnimationUtil, Qubit, CourseCohortSettingsNotificationView, DiscussionTopicsSettingsModel, CourseCohortSettingsModel, AnimationUtil, Qubit, CourseCohortSettingsNotificationView) {
CohortDiscussionsView, CohortCourseWideDiscussionsView, CohortInlineDiscussionsView) {
'use strict'; 'use strict';
describe('Cohorts View', function() { describe('Cohorts View', function() {
...@@ -20,16 +17,7 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers ...@@ -20,16 +17,7 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
getAddModal, selectContentGroup, clearContentGroup, getAddModal, selectContentGroup, clearContentGroup,
saveFormAndExpectErrors, createMockCohortSettings, MOCK_COHORTED_USER_PARTITION_ID, saveFormAndExpectErrors, createMockCohortSettings, MOCK_COHORTED_USER_PARTITION_ID,
MOCK_UPLOAD_COHORTS_CSV_URL, MOCK_STUDIO_ADVANCED_SETTINGS_URL, MOCK_STUDIO_GROUP_CONFIGURATIONS_URL, 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, 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_MANUAL_ASSIGNMENT = 'manual'; MOCK_MANUAL_ASSIGNMENT = 'manual';
MOCK_RANDOM_ASSIGNMENT = 'random'; MOCK_RANDOM_ASSIGNMENT = 'random';
...@@ -70,66 +58,16 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers ...@@ -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 { return {
id: 0, id: 0,
is_cohorted: isCohorted || false, is_cohorted: isCohorted || false
cohorted_inline_discussions: cohortedInlineDiscussions || [],
cohorted_course_wide_discussions: cohortedCourseWideDiscussions || [],
always_cohort_inline_discussions: alwaysCohortInlineDiscussions || false
}; };
}; };
createMockCohortSettings = function(isCohorted, cohortedInlineDiscussions, cohortedCourseWideDiscussions, alwaysCohortInlineDiscussions) { createMockCohortSettings = function(isCohorted) {
return new CourseCohortSettingsModel( return new CourseCohortSettingsModel(
createMockCohortSettingsJson(isCohorted, cohortedInlineDiscussions, cohortedCourseWideDiscussions, alwaysCohortInlineDiscussions) createMockCohortSettingsJson(isCohorted)
);
};
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)
); );
}; };
...@@ -146,7 +84,7 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers ...@@ -146,7 +84,7 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
}; };
createCohortsView = function(test, options) { createCohortsView = function(test, options) {
var cohortsJson, cohorts, contentGroups, cohortSettings, cohortDiscussions; var cohortsJson, cohorts, contentGroups, cohortSettings;
options = options || {}; options = options || {};
cohortsJson = options.cohorts ? {cohorts: options.cohorts} : createMockCohorts(); cohortsJson = options.cohorts ? {cohorts: options.cohorts} : createMockCohorts();
cohorts = new CohortCollection(cohortsJson, {parse: true}); cohorts = new CohortCollection(cohortsJson, {parse: true});
...@@ -155,16 +93,12 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers ...@@ -155,16 +93,12 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
cohortSettings.url = '/mock_service/cohorts/settings'; cohortSettings.url = '/mock_service/cohorts/settings';
cohorts.url = '/mock_service/cohorts'; cohorts.url = '/mock_service/cohorts';
cohortDiscussions = options.cohortDiscussions || createMockCohortDiscussions();
cohortDiscussions.url = '/mock_service/cohorts/discussion/topics';
requests = AjaxHelpers.requests(test); requests = AjaxHelpers.requests(test);
cohortsView = new CohortsView({ cohortsView = new CohortsView({
model: cohorts, model: cohorts,
contentGroups: contentGroups, contentGroups: contentGroups,
cohortSettings: cohortSettings, cohortSettings: cohortSettings,
context: { context: {
discussionTopicsSettingsModel: cohortDiscussions,
uploadCohortsCsvUrl: MOCK_UPLOAD_COHORTS_CSV_URL, uploadCohortsCsvUrl: MOCK_UPLOAD_COHORTS_CSV_URL,
studioAdvancedSettingsUrl: MOCK_STUDIO_ADVANCED_SETTINGS_URL, studioAdvancedSettingsUrl: MOCK_STUDIO_ADVANCED_SETTINGS_URL,
studioGroupConfigurationsUrl: MOCK_STUDIO_GROUP_CONFIGURATIONS_URL, studioGroupConfigurationsUrl: MOCK_STUDIO_GROUP_CONFIGURATIONS_URL,
...@@ -314,40 +248,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers ...@@ -314,40 +248,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
verifyDetailedMessage(expectedTitle, 'error', errors); 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) { unknownUserMessage = function(name) {
return 'Unknown user: ' + name; return 'Unknown user: ' + name;
}; };
...@@ -364,10 +264,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers ...@@ -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/cohort-group-header');
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/notification'); 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-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'); TemplateHelpers.installTemplate('templates/file-upload');
}); });
...@@ -381,8 +277,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers ...@@ -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. // If no cohorts have been created, can't upload a CSV file.
expect(cohortsView.$('.wrapper-cohort-supplemental')).toHaveClass('is-hidden'); 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() { 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 ...@@ -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."); .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() { describe('Cohort Selector', function() {
it('has no initial selection', function() { it('has no initial selection', function() {
createCohortsView(this); createCohortsView(this);
...@@ -1172,375 +1062,5 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers ...@@ -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);
});
});
});
}); });
}); });
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);
});
});
});
});
});
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
'js/edxnotes/views/page_factory', 'js/edxnotes/views/page_factory',
'js/financial-assistance/financial_assistance_form_factory', 'js/financial-assistance/financial_assistance_form_factory',
'js/groups/views/cohorts_dashboard_factory', 'js/groups/views/cohorts_dashboard_factory',
'js/discussions_management/views/discussions_dashboard_factory',
'js/header_factory', 'js/header_factory',
'js/learner_dashboard/program_details_factory', 'js/learner_dashboard/program_details_factory',
'js/learner_dashboard/program_list_factory', 'js/learner_dashboard/program_list_factory',
......
...@@ -731,6 +731,7 @@ ...@@ -731,6 +731,7 @@
'js/spec/edxnotes/views/visibility_decorator_spec.js', 'js/spec/edxnotes/views/visibility_decorator_spec.js',
'js/spec/financial-assistance/financial_assistance_form_view_spec.js', 'js/spec/financial-assistance/financial_assistance_form_view_spec.js',
'js/spec/groups/views/cohorts_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_bulk_exception_spec.js',
'js/spec/instructor_dashboard/certificates_exception_spec.js', 'js/spec/instructor_dashboard/certificates_exception_spec.js',
'js/spec/instructor_dashboard/certificates_invalidation_spec.js', 'js/spec/instructor_dashboard/certificates_invalidation_spec.js',
......
...@@ -1181,49 +1181,6 @@ ...@@ -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 .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-no-list;
@extend %ui-depth1; @extend %ui-depth1;
...@@ -1280,6 +1237,77 @@ ...@@ -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 - student admin
// -------------------- // --------------------
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<label> <label>
<input data-id="<%- id %>" class="check-discussion-subcategory-<%- type %>" type="checkbox" <%- is_divided ? 'checked="checked"' : '' %> /> <input data-id="<%- id %>" class="check-discussion-subcategory-<%- type %>" type="checkbox" <%- is_divided ? 'checked="checked"' : '' %> />
<span class="topic-name"><%- name %></span> <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> </label>
</div> </div>
</li> </li>
...@@ -13,7 +13,6 @@ from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_ ...@@ -13,7 +13,6 @@ from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_
data-cohorts_url="${section_data['cohorts_url']}" data-cohorts_url="${section_data['cohorts_url']}"
data-upload_cohorts_csv_url="${section_data['upload_cohorts_csv_url']}" data-upload_cohorts_csv_url="${section_data['upload_cohorts_csv_url']}"
data-course_cohort_settings_url="${section_data['course_cohort_settings_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-verified_track_cohorting_url="${section_data['verified_track_cohorting_url']}"
data-is_ccx_enabled="${'true' if section_data['ccx_is_enabled'] else 'false'}" data-is_ccx_enabled="${'true' if section_data['ccx_is_enabled'] else 'false'}"
> >
......
...@@ -49,15 +49,5 @@ ...@@ -49,15 +49,5 @@
%> %>
</p> </p>
</div> </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> </div>
<% } %> <% } %>
<!-- 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>
<%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>
<h3 class="hd hd-3 subsection-title"><%- gettext('Specify whether discussion topics are divided by cohort') %></h3> <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"> <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-fields">
<div class="form-field"> <div class="form-field">
<div class="course-wide-discussion-topics"> <div class="course-wide-discussion-topics">
......
<hr class="divider divider-lv1" /> <hr class="divider divider-lv1" />
<form action="" method="post" id="cohort-inline-discussions-form" class="cohort-inline-discussions-form"> <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-fields">
<div class="form-field"> <div class="form-field">
<div class="inline-discussion-topics"> <div class="inline-discussion-topics">
<h4 class="hd hd-4 subsection-title"><%- gettext('Content-Specific Discussion Topics') %></h4> <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> <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> <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> <span class="all-inline-discussions"><%- gettext('Always cohort content-specific discussion topics') %></span>
</label> </label>
</div> </div>
<div class="cohort_inline_discussions"> <div class="divide_inline_discussions">
<label> <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> <span class="all-inline-discussions"><%- gettext('Cohort selected content-specific discussion topics') %></span>
</label> </label>
</div> </div>
......
...@@ -81,7 +81,7 @@ from openedx.core.djangolib.markup import HTML ...@@ -81,7 +81,7 @@ from openedx.core.djangolib.markup import HTML
## Include Underscore templates ## Include Underscore templates
<%block name="header_extras"> <%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"> <script type="text/template" id="${template_name}-tpl">
<%static:include path="instructor/instructor_dashboard_2/${template_name}.underscore" /> <%static:include path="instructor/instructor_dashboard_2/${template_name}.underscore" />
</script> </script>
......
...@@ -504,6 +504,15 @@ urlpatterns += ( ...@@ -504,6 +504,15 @@ urlpatterns += (
include(COURSE_URLS) 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 # Cohorts management
url( url(
r'^courses/{}/cohorts/settings$'.format( r'^courses/{}/cohorts/settings$'.format(
...@@ -548,11 +557,11 @@ urlpatterns += ( ...@@ -548,11 +557,11 @@ urlpatterns += (
name='debug_cohort_mgmt', name='debug_cohort_mgmt',
), ),
url( url(
r'^courses/{}/cohorts/topics$'.format( r'^courses/{}/discussion/topics$'.format(
settings.COURSE_KEY_PATTERN, settings.COURSE_KEY_PATTERN,
), ),
'openedx.core.djangoapps.course_groups.views.cohort_discussion_topics', 'openedx.core.djangoapps.course_groups.views.discussion_topics',
name='cohort_discussion_topics', name='discussion_topics',
), ),
url( url(
r'^courses/{}/verified_track_content/settings'.format( r'^courses/{}/verified_track_content/settings'.format(
......
...@@ -121,6 +121,16 @@ def is_course_cohorted(course_key): ...@@ -121,6 +121,16 @@ def is_course_cohorted(course_key):
return _get_course_cohort_settings(course_key).is_cohorted 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): def set_course_cohorted(course_key, cohorted):
""" """
Given a course course and a boolean, sets whether or not the course is cohorted. Given a course course and a boolean, sets whether or not the course is cohorted.
......
...@@ -136,24 +136,18 @@ def config_course_cohorts_legacy( ...@@ -136,24 +136,18 @@ def config_course_cohorts_legacy(
# pylint: disable=dangerous-default-value # pylint: disable=dangerous-default-value
def config_course_cohorts( def config_course_discussions(
course, course,
is_cohorted, discussion_topics={},
auto_cohorts=[],
manual_cohorts=[],
discussion_topics=[],
divided_discussions=[], divided_discussions=[],
always_divide_inline_discussions=False always_divide_inline_discussions=False
): ):
""" """
Set discussions and configure cohorts for a course. Set discussions and configure divided discussions for a course.
Arguments: Arguments:
course: CourseDescriptor course: CourseDescriptor
is_cohorted (bool): Is the course cohorted? discussion_topics (Dict): Discussion topic names. Picks ids and
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. sort_keys automatically.
divided_discussions: Discussion topics to divide. Converts the divided_discussions: Discussion topics to divide. Converts the
list to use the same ids as discussion topic names. list to use the same ids as discussion topic names.
...@@ -163,11 +157,11 @@ def config_course_cohorts( ...@@ -163,11 +157,11 @@ def config_course_cohorts(
Returns: Returns:
Nothing -- modifies course in place. Nothing -- modifies course in place.
""" """
def to_id(name): def to_id(name):
"""Convert name to id.""" """Convert name to id."""
return topic_name_to_id(course, name) return topic_name_to_id(course, name)
set_course_cohorted(course.id, is_cohorted)
set_course_discussion_settings( set_course_discussion_settings(
course.id, course.id,
divided_discussions=[to_id(name) for name in divided_discussions], divided_discussions=[to_id(name) for name in divided_discussions],
...@@ -175,6 +169,41 @@ def config_course_cohorts( ...@@ -175,6 +169,41 @@ def config_course_cohorts(
division_scheme=CourseDiscussionSettings.COHORT, 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=[],
):
"""
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.
Returns:
Nothing -- modifies course in place.
"""
set_course_cohorted(course.id, is_cohorted)
set_course_discussion_settings(
course.id,
division_scheme=CourseDiscussionSettings.COHORT,
)
for cohort_name in auto_cohorts: for cohort_name in auto_cohorts:
cohort = CohortFactory(course_id=course.id, name=cohort_name) cohort = CohortFactory(course_id=course.id, name=cohort_name)
CourseCohortFactory(course_user_group=cohort, assignment_type=CourseCohort.RANDOM) CourseCohortFactory(course_user_group=cohort, assignment_type=CourseCohort.RANDOM)
...@@ -183,8 +212,6 @@ def config_course_cohorts( ...@@ -183,8 +212,6 @@ def config_course_cohorts(
cohort = CohortFactory(course_id=course.id, name=cohort_name) cohort = CohortFactory(course_id=course.id, name=cohort_name)
CourseCohortFactory(course_user_group=cohort, assignment_type=CourseCohort.MANUAL) 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: try:
# Not implemented for XMLModulestore, which is used by test_cohorts. # Not implemented for XMLModulestore, which is used by test_cohorts.
modulestore().update_item(course, ModuleStoreEnum.UserID.test) modulestore().update_item(course, ModuleStoreEnum.UserID.test)
......
...@@ -25,15 +25,17 @@ from openedx.core.djangolib.testing.utils import skip_unless_lms ...@@ -25,15 +25,17 @@ from openedx.core.djangolib.testing.utils import skip_unless_lms
from ..models import CourseUserGroup, CourseCohort from ..models import CourseUserGroup, CourseCohort
from ..views import ( from ..views import (
course_cohort_settings_handler, cohort_handler, users_in_cohort, add_users_to_cohort, remove_user_from_cohort, course_cohort_settings_handler, course_discussions_settings_handler,
link_cohort_to_partition_group, cohort_discussion_topics cohort_handler, users_in_cohort,
add_users_to_cohort, remove_user_from_cohort,
link_cohort_to_partition_group, discussion_topics
) )
from ..cohorts import ( from ..cohorts import (
get_cohort, get_cohort_by_name, get_cohort_by_id, get_cohort, get_cohort_by_name, get_cohort_by_id,
DEFAULT_COHORT_NAME, get_group_info_for_cohort DEFAULT_COHORT_NAME, get_group_info_for_cohort
) )
from .helpers import ( 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): ...@@ -99,13 +101,13 @@ class CohortViewsTestCase(ModuleStoreTestCase):
view_args.insert(0, request) view_args.insert(0, request)
self.assertRaises(Http404, view, *view_args) 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'] divided_inline_discussions = ['Topic A']
cohorted_course_wide_discussions = ["Topic B"] divided_course_wide_discussions = ["Topic B"]
cohorted_discussions = cohorted_inline_discussions + cohorted_course_wide_discussions divided_discussions = divided_inline_discussions + divided_course_wide_discussions
# inline discussion # inline discussion
ItemFactory.create( ItemFactory.create(
...@@ -124,10 +126,14 @@ class CohortViewsTestCase(ModuleStoreTestCase): ...@@ -124,10 +126,14 @@ class CohortViewsTestCase(ModuleStoreTestCase):
config_course_cohorts( config_course_cohorts(
self.course, self.course,
is_cohorted=True, is_cohorted=True,
)
config_course_discussions(
self.course,
discussion_topics=discussion_topics, discussion_topics=discussion_topics,
divided_discussions=cohorted_discussions divided_discussions=divided_discussions
) )
return cohorted_inline_discussions, cohorted_course_wide_discussions return divided_inline_discussions, divided_course_wide_discussions
def get_handler(self, course, cohort=None, expected_response_code=200, handler=cohort_handler): def get_handler(self, course, cohort=None, expected_response_code=200, handler=cohort_handler):
""" """
...@@ -178,108 +184,74 @@ class CohortViewsTestCase(ModuleStoreTestCase): ...@@ -178,108 +184,74 @@ class CohortViewsTestCase(ModuleStoreTestCase):
@attr(shard=2) @attr(shard=2)
class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase): class CourseDiscussionsHandlerTestCase(CohortViewsTestCase):
""" """
Tests the `course_cohort_settings_handler` view. Tests the course_discussion_settings_handler
""" """
def get_expected_response(self): def get_expected_response(self):
""" """
Returns the static response dict. Returns the static response dict.
""" """
return { return {
'is_cohorted': True, u'always_divide_inline_discussions': False,
'always_cohort_inline_discussions': False, u'divided_inline_discussions': [],
'cohorted_inline_discussions': [], u'divided_course_wide_discussions': [],
'cohorted_course_wide_discussions': [], u'id': 1
'id': 1
} }
def test_non_staff(self): def test_non_staff(self):
""" """
Verify that we cannot access course_cohort_settings_handler if we're a non-staff user. Verify that we cannot access course_discussions_settings_handler if we're a non-staff user.
"""
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.
""" """
config_course_cohorts(self.course, is_cohorted=True) 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)])
response = self.get_handler(self.course, handler=course_cohort_settings_handler)
expected_response = self.get_expected_response()
self.assertEqual(response, expected_response)
expected_response['is_cohorted'] = False
response = self.patch_handler(self.course, data=expected_response, handler=course_cohort_settings_handler)
self.assertEqual(response, expected_response)
def test_update_always_cohort_inline_discussion_settings(self): def test_update_always_divide_inline_discussion_settings(self):
""" """
Verify that course_cohort_settings_handler is working for always_cohort_inline_discussions via HTTP PATCH. Verify that course_discussions_settings_handler is working for always_divide_inline_discussions via HTTP PATCH.
""" """
config_course_cohorts(self.course, is_cohorted=True) config_course_cohorts(self.course, is_cohorted=True)
response = self.get_handler(self.course, handler=course_cohort_settings_handler) response = self.get_handler(self.course, handler=course_discussions_settings_handler)
expected_response = self.get_expected_response() expected_response = self.get_expected_response()
self.assertEqual(response, expected_response) self.assertEqual(response, expected_response)
expected_response['always_cohort_inline_discussions'] = True expected_response['always_divide_inline_discussions'] = True
response = self.patch_handler(self.course, data=expected_response, handler=course_cohort_settings_handler) response = self.patch_handler(self.course, data=expected_response, handler=course_discussions_settings_handler)
self.assertEqual(response, expected_response) self.assertEqual(response, expected_response)
def test_update_course_wide_discussion_settings(self): def test_update_course_wide_discussion_settings(self):
""" """
Verify that course_cohort_settings_handler is working for cohorted_course_wide_discussions via HTTP PATCH. Verify that course_discussions_settings_handler is working for divided_course_wide_discussions via HTTP PATCH.
""" """
# course-wide discussion # course-wide discussion
discussion_topics = { discussion_topics = {
"Topic B": {"id": "Topic B"}, "Topic B": {"id": "Topic B"},
} }
config_course_cohorts(self.course, is_cohorted=True, discussion_topics=discussion_topics) 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_cohort_settings_handler) response = self.get_handler(self.course, handler=course_discussions_settings_handler)
expected_response = self.get_expected_response() expected_response = self.get_expected_response()
self.assertEqual(response, expected_response) self.assertEqual(response, expected_response)
expected_response['cohorted_course_wide_discussions'] = [topic_name_to_id(self.course, "Topic B")] 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_cohort_settings_handler) response = self.patch_handler(self.course, data=expected_response, handler=course_discussions_settings_handler)
self.assertEqual(response, expected_response) self.assertEqual(response, expected_response)
def test_update_inline_discussion_settings(self): def test_update_inline_discussion_settings(self):
""" """
Verify that course_cohort_settings_handler is working for cohorted_inline_discussions via HTTP PATCH. Verify that course_discussions_settings_handler is working for divided_inline_discussions via HTTP PATCH.
""" """
config_course_cohorts(self.course, is_cohorted=True) config_course_cohorts(self.course, is_cohorted=True)
response = self.get_handler(self.course, handler=course_cohort_settings_handler) response = self.get_handler(self.course, handler=course_discussions_settings_handler)
expected_response = self.get_expected_response() expected_response = self.get_expected_response()
self.assertEqual(response, expected_response) self.assertEqual(response, expected_response)
...@@ -295,37 +267,99 @@ class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase): ...@@ -295,37 +267,99 @@ class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
start=now start=now
) )
expected_response['cohorted_inline_discussions'] = ["Topic_A"] expected_response['divided_inline_discussions'] = ["Topic_A"]
response = self.patch_handler(self.course, data=expected_response, handler=course_cohort_settings_handler) response = self.patch_handler(self.course, data=expected_response, handler=course_discussions_settings_handler)
self.assertEqual(response, expected_response) self.assertEqual(response, expected_response)
def test_update_settings_with_missing_field(self): def test_get_settings(self):
""" """
Verify that course_cohort_settings_handler return HTTP 400 if required data field is missing from post data. Verify that course_discussions_settings_handler is working for HTTP GET.
""" """
config_course_cohorts(self.course, is_cohorted=True) divided_inline_discussions, divided_course_wide_discussions = self.create_divided_discussions()
response = self.patch_handler(self.course, expected_response_code=400, handler=course_cohort_settings_handler) response = self.get_handler(self.course, handler=course_discussions_settings_handler)
self.assertEqual("Bad Request", response.get("error")) 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): def test_update_settings_with_invalid_field_data_type(self):
""" """
Verify that course_cohort_settings_handler return HTTP 400 if field data type is incorrect. Verify that course_discussions_settings_handler return HTTP 400 if field data type is incorrect.
""" """
config_course_cohorts(self.course, is_cohorted=True) config_course_cohorts(self.course, is_cohorted=True)
response = self.patch_handler( response = self.patch_handler(
self.course, self.course,
data={'always_cohort_inline_discussions': ''}, data={'always_divide_inline_discussions': ''},
expected_response_code=400, expected_response_code=400,
handler=course_cohort_settings_handler handler=course_discussions_settings_handler
) )
self.assertEqual( self.assertEqual(
"Incorrect field type for `{}`. Type must be `{}`".format('always_divide_inline_discussions', bool.__name__), "Incorrect field type for `{}`. Type must be `{}`".format('always_divide_inline_discussions', bool.__name__),
response.get("error") response.get("error")
) )
@attr(shard=2)
class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
"""
Tests the `course_cohort_settings_handler` view.
"""
def get_expected_response(self):
"""
Returns the static response dict.
"""
return {
'is_cohorted': True,
'id': 1
}
def test_non_staff(self):
"""
Verify that we cannot access course_cohort_settings_handler if we're a non-staff user.
"""
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_update_is_cohorted_settings(self):
"""
Verify that course_cohort_settings_handler is working for is_cohorted 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['is_cohorted'] = False
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.
"""
config_course_cohorts(self.course, is_cohorted=True)
response = self.patch_handler(self.course, expected_response_code=400, handler=course_cohort_settings_handler)
self.assertEqual("Bad Request", response.get("error"))
def test_update_settings_with_invalid_field_data_type(self):
"""
Verify that course_cohort_settings_handler return HTTP 400 if field data type is incorrect.
"""
config_course_cohorts(self.course, is_cohorted=True)
response = self.patch_handler( response = self.patch_handler(
self.course, self.course,
data={'is_cohorted': ''}, data={'is_cohorted': ''},
...@@ -1240,25 +1274,25 @@ class RemoveUserFromCohortTestCase(CohortViewsTestCase): ...@@ -1240,25 +1274,25 @@ class RemoveUserFromCohortTestCase(CohortViewsTestCase):
@attr(shard=2) @attr(shard=2)
@skip_unless_lms @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): 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): 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. # 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'] start_date = response['inline_discussions']['subcategories']['Chapter']['start_date']
expected_response = { expected_response = {
"course_wide_discussions": { "course_wide_discussions": {
......
...@@ -64,20 +64,29 @@ def unlink_cohort_partition_group(cohort): ...@@ -64,20 +64,29 @@ def unlink_cohort_partition_group(cohort):
# pylint: disable=invalid-name # 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. 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( divided_course_wide_discussions, divided_inline_discussions = get_divided_discussions(
course, course_discussion_settings course, course_discussion_settings
) )
return { return {
'id': course_discussion_settings.id, 'id': course_discussion_settings.id,
'is_cohorted': is_cohorted, 'divided_inline_discussions': divided_inline_discussions,
'cohorted_inline_discussions': divided_inline_discussions, 'divided_course_wide_discussions': divided_course_wide_discussions,
'cohorted_course_wide_discussions': divided_course_wide_discussions, 'always_divide_inline_discussions': course_discussion_settings.always_divide_inline_discussions,
'always_cohort_inline_discussions': course_discussion_settings.always_divide_inline_discussions,
} }
...@@ -121,18 +130,17 @@ def get_divided_discussions(course, discussion_settings): ...@@ -121,18 +130,17 @@ def get_divided_discussions(course, discussion_settings):
@ensure_csrf_cookie @ensure_csrf_cookie
@expect_json @expect_json
@login_required @login_required
def course_cohort_settings_handler(request, course_key_string): def course_discussions_settings_handler(request, course_key_string):
""" """
The restful handler for cohort setting requests. Requires JSON. The restful handler for divided discussion setting requests. Requires JSON.
This will raise 404 if user is not staff. This will raise 404 if user is not staff.
GET GET
Returns the JSON representation of cohort settings for the course. Returns the JSON representation of divided discussion settings for the course.
PATCH PATCH
Updates the cohort settings for the course. Returns the JSON representation of updated settings. Updates the divided discussion settings for the course. Returns the JSON representation of updated settings.
""" """
course_key = CourseKey.from_string(course_key_string) course_key = CourseKey.from_string(course_key_string)
course = get_course_with_access(request.user, 'staff', course_key) 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) discussion_settings = get_course_discussion_settings(course_key)
if request.method == 'PATCH': if request.method == 'PATCH':
...@@ -142,40 +150,72 @@ def course_cohort_settings_handler(request, course_key_string): ...@@ -142,40 +150,72 @@ def course_cohort_settings_handler(request, course_key_string):
settings_to_change = {} settings_to_change = {}
if 'is_cohorted' in request.json: if 'divided_course_wide_discussions' in request.json or 'divided_inline_discussions' 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( divided_course_wide_discussions = request.json.get(
'cohorted_course_wide_discussions', divided_course_wide_discussions 'divided_course_wide_discussions', divided_course_wide_discussions
) )
divided_inline_discussions = request.json.get( divided_inline_discussions = request.json.get(
'cohorted_inline_discussions', divided_inline_discussions 'divided_inline_discussions', divided_inline_discussions
) )
settings_to_change['divided_discussions'] = divided_course_wide_discussions + divided_inline_discussions settings_to_change['divided_discussions'] = divided_course_wide_discussions + divided_inline_discussions
if 'always_cohort_inline_discussions' in request.json: if 'always_divide_inline_discussions' in request.json:
settings_to_change['always_divide_inline_discussions'] = request.json.get( settings_to_change['always_divide_inline_discussions'] = request.json.get(
'always_cohort_inline_discussions' 'always_divide_inline_discussions'
) )
if not settings_to_change: if not settings_to_change:
return JsonResponse({"error": unicode("Bad Request")}, 400) return JsonResponse({"error": unicode("Bad Request")}, 400)
try: 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: if settings_to_change:
discussion_settings = set_course_discussion_settings(course_key, **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
@login_required
def course_cohort_settings_handler(request, course_key_string):
"""
The restful handler for cohort setting requests. Requires JSON.
This will raise 404 if user is not staff.
GET
Returns the JSON representation of cohort settings for the course.
PATCH
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)
if request.method == 'PATCH':
if 'is_cohorted' not in request.json:
return JsonResponse({"error": unicode("Bad Request")}, 400)
is_cohorted = request.json.get('is_cohorted')
try:
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: except ValueError as err:
# Note: error message not translated because it is not exposed to the user (UI prevents this state). # 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({"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")) @require_http_methods(("GET", "PUT", "POST", "PATCH"))
...@@ -428,9 +468,9 @@ def debug_cohort_mgmt(request, course_key_string): ...@@ -428,9 +468,9 @@ def debug_cohort_mgmt(request, course_key_string):
@expect_json @expect_json
@login_required @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. This will raise 404 if user is not staff.
Returns the JSON representation of discussion topics w.r.t categories for the course. Returns the JSON representation of discussion topics w.r.t categories for the course.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment