EDUCATOR-141: Moved the Discussions Mgt to its own tab and updated tests
This commit is contained in:
committed by
cahrens
parent
8951ac8c61
commit
c23c0b991b
@@ -47,6 +47,15 @@ class InstructorDashboardPage(CoursePage):
|
||||
cohort_management_section.wait_for_page()
|
||||
return cohort_management_section
|
||||
|
||||
def select_discussion_management(self):
|
||||
"""
|
||||
Selects the Discussion tab and returns the DiscussionmanagementSection
|
||||
"""
|
||||
self.q(css='[data-section="discussions_management"').first.click()
|
||||
discussion_management_section = DiscussionManagementSection(self.browser)
|
||||
discussion_management_section.wait_for_page()
|
||||
return discussion_management_section
|
||||
|
||||
def select_data_download(self):
|
||||
"""
|
||||
Selects the data download tab and returns a DataDownloadPage.
|
||||
@@ -666,61 +675,31 @@ class CohortManagementSection(PageObject):
|
||||
self.q(css=self._bounded_selector('.cohorts-state')).first.click()
|
||||
self.wait_for_ajax()
|
||||
|
||||
def toggles_showing_of_discussion_topics(self):
|
||||
def cohort_management_controls_visible(self):
|
||||
"""
|
||||
Shows the discussion topics.
|
||||
Return the visibility status of cohort management controls(cohort selector section etc).
|
||||
"""
|
||||
self.q(css=self._bounded_selector(".toggle-cohort-management-discussions")).first.click()
|
||||
self.wait_for_element_visibility("#cohort-discussions-management", "Waiting for discussions to appear")
|
||||
return (self.q(css=self._bounded_selector('.cohort-management-nav')).visible and
|
||||
self.q(css=self._bounded_selector('.wrapper-cohort-supplemental')).visible)
|
||||
|
||||
def discussion_topics_visible(self):
|
||||
"""
|
||||
Returns the visibility status of cohort discussion controls.
|
||||
"""
|
||||
EmptyPromise(
|
||||
lambda: self.q(css=self._bounded_selector('.cohort-discussions-nav')).results != 0,
|
||||
"Waiting for discussion section to show"
|
||||
).fulfill()
|
||||
|
||||
return (self.q(css=self._bounded_selector('.cohort-course-wide-discussions-nav')).visible and
|
||||
self.q(css=self._bounded_selector('.cohort-inline-discussions-nav')).visible)
|
||||
class DiscussionManagementSection(PageObject):
|
||||
|
||||
def select_discussion_topic(self, key):
|
||||
"""
|
||||
Selects discussion topic checkbox by clicking on it.
|
||||
"""
|
||||
self.q(css=self._bounded_selector(".check-discussion-subcategory-%s" % key)).first.click()
|
||||
url = None
|
||||
|
||||
def select_always_inline_discussion(self):
|
||||
"""
|
||||
Selects the always_cohort_inline_discussions radio button.
|
||||
"""
|
||||
self.q(css=self._bounded_selector(".check-all-inline-discussions")).first.click()
|
||||
discussion_form_selectors = {
|
||||
'course-wide': '.cohort-course-wide-discussions-form',
|
||||
'inline': '.cohort-inline-discussions-form'
|
||||
}
|
||||
|
||||
def always_inline_discussion_selected(self):
|
||||
"""
|
||||
Returns true if always_cohort_inline_discussions radio button is selected.
|
||||
"""
|
||||
return len(self.q(css=self._bounded_selector(".check-all-inline-discussions:checked"))) > 0
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css=self.discussion_form_selectors['course-wide']).present
|
||||
|
||||
def cohort_some_inline_discussion_selected(self):
|
||||
def _bounded_selector(self, selector):
|
||||
"""
|
||||
Returns true if some_cohort_inline_discussions radio button is selected.
|
||||
Return `selector`, but limited to the divided discussion management context.
|
||||
"""
|
||||
return len(self.q(css=self._bounded_selector(".check-cohort-inline-discussions:checked"))) > 0
|
||||
|
||||
def select_cohort_some_inline_discussion(self):
|
||||
"""
|
||||
Selects the cohort_some_inline_discussions radio button.
|
||||
"""
|
||||
self.q(css=self._bounded_selector(".check-cohort-inline-discussions")).first.click()
|
||||
|
||||
def inline_discussion_topics_disabled(self):
|
||||
"""
|
||||
Returns the status of inline discussion topics, enabled or disabled.
|
||||
"""
|
||||
inline_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-inline'))
|
||||
return all(topic.get_attribute('disabled') == 'true' for topic in inline_topics)
|
||||
return '.discussions-management {}'.format(selector)
|
||||
|
||||
def is_save_button_disabled(self, key):
|
||||
"""
|
||||
@@ -730,18 +709,36 @@ class CohortManagementSection(PageObject):
|
||||
disabled = self.q(css=self._bounded_selector(save_button_css)).attrs('disabled')
|
||||
return disabled[0] == 'true'
|
||||
|
||||
def is_category_selected(self):
|
||||
def discussion_topics_visible(self):
|
||||
"""
|
||||
Returns the status for category checkboxes.
|
||||
Returns the visibility status of divide discussion controls.
|
||||
"""
|
||||
return self.q(css=self._bounded_selector('.check-discussion-category:checked')).is_present()
|
||||
return (self.q(css=self._bounded_selector('.course-wide-discussions-nav')).visible and
|
||||
self.q(css=self._bounded_selector('.inline-discussions-nav')).visible)
|
||||
|
||||
def get_cohorted_topics_count(self, key):
|
||||
def divided_discussion_heading_is_visible(self, key):
|
||||
"""
|
||||
Returns the count for cohorted topics.
|
||||
Returns the text of discussion topic headings if it exists, otherwise return False.
|
||||
"""
|
||||
cohorted_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-%s:checked' % key))
|
||||
return len(cohorted_topics.results)
|
||||
form_heading_css = '%s %s' % (self.discussion_form_selectors[key], '.subsection-title')
|
||||
discussion_heading = self.q(css=self._bounded_selector(form_heading_css))
|
||||
|
||||
if len(discussion_heading) == 0:
|
||||
return False
|
||||
return discussion_heading.first.text[0]
|
||||
|
||||
def select_always_inline_discussion(self):
|
||||
"""
|
||||
Selects the always_divide_inline_discussions radio button.
|
||||
"""
|
||||
self.q(css=self._bounded_selector(".check-all-inline-discussions")).first.click()
|
||||
|
||||
def inline_discussion_topics_disabled(self):
|
||||
"""
|
||||
Returns the status of inline discussion topics, enabled or disabled.
|
||||
"""
|
||||
inline_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-inline'))
|
||||
return all(topic.get_attribute('disabled') == 'true' for topic in inline_topics)
|
||||
|
||||
def save_discussion_topics(self, key):
|
||||
"""
|
||||
@@ -750,7 +747,38 @@ class CohortManagementSection(PageObject):
|
||||
save_button_css = '%s %s' % (self.discussion_form_selectors[key], '.action-save')
|
||||
self.q(css=self._bounded_selector(save_button_css)).first.click()
|
||||
|
||||
def get_cohort_discussions_message(self, key, msg_type="confirmation"):
|
||||
def always_inline_discussion_selected(self):
|
||||
"""
|
||||
Returns true if always_divide_inline_discussions radio button is selected.
|
||||
"""
|
||||
return len(self.q(css=self._bounded_selector(".check-all-inline-discussions:checked"))) > 0
|
||||
|
||||
def divide_some_inline_discussion_selected(self):
|
||||
"""
|
||||
Returns true if divide_some_inline_discussions radio button is selected.
|
||||
"""
|
||||
return len(self.q(css=self._bounded_selector(".check-cohort-inline-discussions:checked"))) > 0
|
||||
|
||||
def select_divide_some_inline_discussion(self):
|
||||
"""
|
||||
Selects the divide_some_inline_discussions radio button.
|
||||
"""
|
||||
self.q(css=self._bounded_selector(".check-cohort-inline-discussions")).first.click()
|
||||
|
||||
def get_divided_topics_count(self, key):
|
||||
"""
|
||||
Returns the count for divided topics.
|
||||
"""
|
||||
divided_topics = self.q(css=self._bounded_selector('.check-discussion-subcategory-%s:checked' % key))
|
||||
return len(divided_topics.results)
|
||||
|
||||
def select_discussion_topic(self, key):
|
||||
"""
|
||||
Selects discussion topic checkbox by clicking on it.
|
||||
"""
|
||||
self.q(css=self._bounded_selector(".check-discussion-subcategory-%s" % key)).first.click()
|
||||
|
||||
def get_divide_discussions_message(self, key, msg_type="confirmation"):
|
||||
"""
|
||||
Returns the message related to modifying discussion topics.
|
||||
"""
|
||||
@@ -767,23 +795,11 @@ class CohortManagementSection(PageObject):
|
||||
return ''
|
||||
return message_title.first.text[0]
|
||||
|
||||
def cohort_discussion_heading_is_visible(self, key):
|
||||
def is_category_selected(self):
|
||||
"""
|
||||
Returns the visibility of discussion topic headings.
|
||||
Returns the status for category checkboxes.
|
||||
"""
|
||||
form_heading_css = '%s %s' % (self.discussion_form_selectors[key], '.subsection-title')
|
||||
discussion_heading = self.q(css=self._bounded_selector(form_heading_css))
|
||||
|
||||
if len(discussion_heading) == 0:
|
||||
return False
|
||||
return discussion_heading.first.text[0]
|
||||
|
||||
def cohort_management_controls_visible(self):
|
||||
"""
|
||||
Return the visibility status of cohort management controls(cohort selector section etc).
|
||||
"""
|
||||
return (self.q(css=self._bounded_selector('.cohort-management-nav')).visible and
|
||||
self.q(css=self._bounded_selector('.wrapper-cohort-supplemental')).visible)
|
||||
return self.q(css=self._bounded_selector('.check-discussion-category:checked')).is_present()
|
||||
|
||||
|
||||
class MembershipPageAutoEnrollSection(PageObject):
|
||||
|
||||
@@ -76,11 +76,16 @@ class CohortTestMixin(object):
|
||||
|
||||
def enable_cohorting(self, course_fixture):
|
||||
"""
|
||||
enables cohorting for the current course fixture.
|
||||
enables cohorts and always_divide_inline_discussions for the current course fixture.
|
||||
"""
|
||||
url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/cohorts/settings' # pylint: disable=protected-access
|
||||
data = json.dumps({'always_cohort_inline_discussions': True})
|
||||
discussions_url = LMS_BASE_URL + "/courses/" + course_fixture._course_key + '/discussions/settings' # pylint: disable=protected-access
|
||||
|
||||
data = json.dumps({'is_cohorted': True})
|
||||
discussions_data = json.dumps({'always_divide_inline_discussions': True})
|
||||
|
||||
response = course_fixture.session.patch(url, data=data, headers=course_fixture.headers)
|
||||
course_fixture.session.patch(discussions_url, data=discussions_data, headers=course_fixture.headers)
|
||||
|
||||
def disable_cohorting(self, course_fixture):
|
||||
"""
|
||||
|
||||
@@ -693,267 +693,6 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin
|
||||
self.cohort_management_page.a11y_audit.check_for_accessibility_errors()
|
||||
|
||||
|
||||
@attr(shard=6)
|
||||
class CohortDiscussionTopicsTest(UniqueCourseTest, CohortTestMixin):
|
||||
"""
|
||||
Tests for cohorting the inline and course-wide discussion topics.
|
||||
"""
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up a discussion topics
|
||||
"""
|
||||
super(CohortDiscussionTopicsTest, self).setUp()
|
||||
|
||||
self.discussion_id = "test_discussion_{}".format(uuid.uuid4().hex)
|
||||
self.course_fixture = CourseFixture(**self.course_info).add_children(
|
||||
XBlockFixtureDesc("chapter", "Test Section").add_children(
|
||||
XBlockFixtureDesc("sequential", "Test Subsection").add_children(
|
||||
XBlockFixtureDesc("vertical", "Test Unit").add_children(
|
||||
XBlockFixtureDesc(
|
||||
"discussion",
|
||||
"Test Discussion",
|
||||
metadata={"discussion_id": self.discussion_id}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
).install()
|
||||
|
||||
# create course with single cohort and two content groups (user_partition of type "cohort")
|
||||
self.cohort_name = "OnlyCohort"
|
||||
self.setup_cohort_config(self.course_fixture)
|
||||
self.cohort_id = self.add_manual_cohort(self.course_fixture, self.cohort_name)
|
||||
|
||||
# login as an instructor
|
||||
self.instructor_name = "instructor_user"
|
||||
self.instructor_id = AutoAuthPage(
|
||||
self.browser, username=self.instructor_name, email="instructor_user@example.com",
|
||||
course_id=self.course_id, staff=True
|
||||
).visit().get_user_id()
|
||||
|
||||
# go to the membership page on the instructor dashboard
|
||||
self.instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)
|
||||
self.instructor_dashboard_page.visit()
|
||||
self.cohort_management_page = self.instructor_dashboard_page.select_cohort_management()
|
||||
self.cohort_management_page.wait_for_page()
|
||||
|
||||
self.course_wide_key = 'course-wide'
|
||||
self.inline_key = 'inline'
|
||||
|
||||
def cohort_discussion_topics_are_visible(self):
|
||||
"""
|
||||
Assert that discussion topics are visible with appropriate content.
|
||||
"""
|
||||
self.cohort_management_page.toggles_showing_of_discussion_topics()
|
||||
self.assertTrue(self.cohort_management_page.discussion_topics_visible())
|
||||
|
||||
self.assertEqual(
|
||||
"Course-Wide Discussion Topics",
|
||||
self.cohort_management_page.cohort_discussion_heading_is_visible(self.course_wide_key)
|
||||
)
|
||||
self.assertTrue(self.cohort_management_page.is_save_button_disabled(self.course_wide_key))
|
||||
|
||||
self.assertEqual(
|
||||
"Content-Specific Discussion Topics",
|
||||
self.cohort_management_page.cohort_discussion_heading_is_visible(self.inline_key)
|
||||
)
|
||||
self.assertTrue(self.cohort_management_page.is_save_button_disabled(self.inline_key))
|
||||
|
||||
def save_and_verify_discussion_topics(self, key):
|
||||
"""
|
||||
Saves the discussion topics and the verify the changes.
|
||||
"""
|
||||
# click on the inline save button.
|
||||
self.cohort_management_page.save_discussion_topics(key)
|
||||
|
||||
# verifies that changes saved successfully.
|
||||
confirmation_message = self.cohort_management_page.get_cohort_discussions_message(key=key)
|
||||
self.assertEqual("Your changes have been saved.", confirmation_message)
|
||||
|
||||
# save button disabled again.
|
||||
self.assertTrue(self.cohort_management_page.is_save_button_disabled(key))
|
||||
|
||||
def reload_page(self):
|
||||
"""
|
||||
Refresh the page.
|
||||
"""
|
||||
self.browser.refresh()
|
||||
self.cohort_management_page.wait_for_page()
|
||||
|
||||
self.instructor_dashboard_page.select_cohort_management()
|
||||
self.cohort_management_page.wait_for_page()
|
||||
|
||||
self.cohort_discussion_topics_are_visible()
|
||||
|
||||
def verify_discussion_topics_after_reload(self, key, cohorted_topics):
|
||||
"""
|
||||
Verifies the changed topics.
|
||||
"""
|
||||
self.reload_page()
|
||||
self.assertEqual(self.cohort_management_page.get_cohorted_topics_count(key), cohorted_topics)
|
||||
|
||||
def test_cohort_course_wide_discussion_topic(self):
|
||||
"""
|
||||
Scenario: cohort a course-wide discussion topic.
|
||||
|
||||
Given I have a course with a cohort defined,
|
||||
And a course-wide discussion with disabled Save button.
|
||||
When I click on the course-wide discussion topic
|
||||
Then I see the enabled save button
|
||||
When I click on save button
|
||||
Then I see success message
|
||||
When I reload the page
|
||||
Then I see the discussion topic selected
|
||||
"""
|
||||
self.cohort_discussion_topics_are_visible()
|
||||
|
||||
cohorted_topics_before = self.cohort_management_page.get_cohorted_topics_count(self.course_wide_key)
|
||||
self.cohort_management_page.select_discussion_topic(self.course_wide_key)
|
||||
|
||||
self.assertFalse(self.cohort_management_page.is_save_button_disabled(self.course_wide_key))
|
||||
|
||||
self.save_and_verify_discussion_topics(key=self.course_wide_key)
|
||||
cohorted_topics_after = self.cohort_management_page.get_cohorted_topics_count(self.course_wide_key)
|
||||
|
||||
self.assertNotEqual(cohorted_topics_before, cohorted_topics_after)
|
||||
|
||||
self.verify_discussion_topics_after_reload(self.course_wide_key, cohorted_topics_after)
|
||||
|
||||
def test_always_cohort_inline_topic_enabled(self):
|
||||
"""
|
||||
Scenario: Select the always_cohort_inline_topics radio button
|
||||
|
||||
Given I have a course with a cohort defined,
|
||||
And an inline discussion topic with disabled Save button.
|
||||
When I click on always_cohort_inline_topics
|
||||
Then I see enabled save button
|
||||
And I see disabled inline discussion topics
|
||||
When I save the change
|
||||
And I reload the page
|
||||
Then I see the always_cohort_inline_topics option enabled
|
||||
"""
|
||||
self.cohort_discussion_topics_are_visible()
|
||||
|
||||
# enable always inline discussion topics and save the change
|
||||
self.cohort_management_page.select_always_inline_discussion()
|
||||
self.assertFalse(self.cohort_management_page.is_save_button_disabled(self.inline_key))
|
||||
self.assertTrue(self.cohort_management_page.inline_discussion_topics_disabled())
|
||||
self.cohort_management_page.save_discussion_topics(key=self.inline_key)
|
||||
|
||||
self.reload_page()
|
||||
self.assertTrue(self.cohort_management_page.always_inline_discussion_selected())
|
||||
|
||||
def test_cohort_some_inline_topics_enabled(self):
|
||||
"""
|
||||
Scenario: Select the cohort_some_inline_topics radio button
|
||||
|
||||
Given I have a course with a cohort defined and always_cohort_inline_topics set to True
|
||||
And an inline discussion topic with disabled Save button.
|
||||
When I click on cohort_some_inline_topics
|
||||
Then I see enabled save button
|
||||
And I see enabled inline discussion topics
|
||||
When I save the change
|
||||
And I reload the page
|
||||
Then I see the cohort_some_inline_topics option enabled
|
||||
"""
|
||||
self.cohort_discussion_topics_are_visible()
|
||||
# By default always inline discussion topics is False. Enable it (and reload the page).
|
||||
self.assertFalse(self.cohort_management_page.always_inline_discussion_selected())
|
||||
self.cohort_management_page.select_always_inline_discussion()
|
||||
self.cohort_management_page.save_discussion_topics(key=self.inline_key)
|
||||
self.reload_page()
|
||||
self.assertFalse(self.cohort_management_page.cohort_some_inline_discussion_selected())
|
||||
|
||||
# enable some inline discussion topic radio button.
|
||||
self.cohort_management_page.select_cohort_some_inline_discussion()
|
||||
# I see that save button is enabled
|
||||
self.assertFalse(self.cohort_management_page.is_save_button_disabled(self.inline_key))
|
||||
# I see that inline discussion topics are enabled
|
||||
self.assertFalse(self.cohort_management_page.inline_discussion_topics_disabled())
|
||||
self.cohort_management_page.save_discussion_topics(key=self.inline_key)
|
||||
|
||||
self.reload_page()
|
||||
self.assertTrue(self.cohort_management_page.cohort_some_inline_discussion_selected())
|
||||
|
||||
def test_cohort_inline_discussion_topic(self):
|
||||
"""
|
||||
Scenario: cohort inline discussion topic.
|
||||
|
||||
Given I have a course with a cohort defined,
|
||||
And a inline discussion topic with disabled Save button
|
||||
And When I click on inline discussion topic
|
||||
And I see enabled save button
|
||||
And When i click save button
|
||||
Then I see success message
|
||||
When I reload the page
|
||||
Then I see the discussion topic selected
|
||||
"""
|
||||
self.cohort_discussion_topics_are_visible()
|
||||
|
||||
cohorted_topics_before = self.cohort_management_page.get_cohorted_topics_count(self.inline_key)
|
||||
# check the discussion topic.
|
||||
self.cohort_management_page.select_discussion_topic(self.inline_key)
|
||||
|
||||
# Save button enabled.
|
||||
self.assertFalse(self.cohort_management_page.is_save_button_disabled(self.inline_key))
|
||||
|
||||
# verifies that changes saved successfully.
|
||||
self.save_and_verify_discussion_topics(key=self.inline_key)
|
||||
|
||||
cohorted_topics_after = self.cohort_management_page.get_cohorted_topics_count(self.inline_key)
|
||||
self.assertNotEqual(cohorted_topics_before, cohorted_topics_after)
|
||||
|
||||
self.verify_discussion_topics_after_reload(self.inline_key, cohorted_topics_after)
|
||||
|
||||
def test_verify_that_selecting_the_final_child_selects_category(self):
|
||||
"""
|
||||
Scenario: Category should be selected on selecting final child.
|
||||
|
||||
Given I have a course with a cohort defined,
|
||||
And a inline discussion with disabled Save button.
|
||||
When I click on child topics
|
||||
Then I see enabled saved button
|
||||
Then I see parent category to be checked.
|
||||
"""
|
||||
self.cohort_discussion_topics_are_visible()
|
||||
|
||||
# category should not be selected.
|
||||
self.assertFalse(self.cohort_management_page.is_category_selected())
|
||||
|
||||
# check the discussion topic.
|
||||
self.cohort_management_page.select_discussion_topic(self.inline_key)
|
||||
|
||||
# verify that category is selected.
|
||||
self.assertTrue(self.cohort_management_page.is_category_selected())
|
||||
|
||||
def test_verify_that_deselecting_the_final_child_deselects_category(self):
|
||||
"""
|
||||
Scenario: Category should be deselected on deselecting final child.
|
||||
|
||||
Given I have a course with a cohort defined,
|
||||
And a inline discussion with disabled Save button.
|
||||
When I click on final child topics
|
||||
Then I see enabled saved button
|
||||
Then I see parent category to be deselected.
|
||||
"""
|
||||
self.cohort_discussion_topics_are_visible()
|
||||
|
||||
# category should not be selected.
|
||||
self.assertFalse(self.cohort_management_page.is_category_selected())
|
||||
|
||||
# check the discussion topic.
|
||||
self.cohort_management_page.select_discussion_topic(self.inline_key)
|
||||
|
||||
# verify that category is selected.
|
||||
self.assertTrue(self.cohort_management_page.is_category_selected())
|
||||
|
||||
# un-check the discussion topic.
|
||||
self.cohort_management_page.select_discussion_topic(self.inline_key)
|
||||
|
||||
# category should not be selected.
|
||||
self.assertFalse(self.cohort_management_page.is_category_selected())
|
||||
|
||||
|
||||
@attr(shard=6)
|
||||
class CohortContentGroupAssociationTest(UniqueCourseTest, CohortTestMixin):
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
End-to-end tests related to the divided discussion management on the LMS Instructor Dashboard
|
||||
"""
|
||||
|
||||
from nose.plugins.attrib import attr
|
||||
from common.test.acceptance.tests.discussion.helpers import CohortTestMixin
|
||||
from common.test.acceptance.tests.helpers import UniqueCourseTest
|
||||
from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc
|
||||
from common.test.acceptance.pages.lms.auto_auth import AutoAuthPage
|
||||
from common.test.acceptance.pages.lms.instructor_dashboard import InstructorDashboardPage
|
||||
|
||||
import uuid
|
||||
|
||||
|
||||
@attr(shard=6)
|
||||
class DividedDiscussionTopicsTest(UniqueCourseTest, CohortTestMixin):
|
||||
"""
|
||||
Tests for dividing the inline and course-wide discussion topics.
|
||||
"""
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up a discussion topic
|
||||
"""
|
||||
super(DividedDiscussionTopicsTest, self).setUp()
|
||||
|
||||
self.discussion_id = "test_discussion_{}".format(uuid.uuid4().hex)
|
||||
self.course_fixture = CourseFixture(**self.course_info).add_children(
|
||||
XBlockFixtureDesc("chapter", "Test Section").add_children(
|
||||
XBlockFixtureDesc("sequential", "Test Subsection").add_children(
|
||||
XBlockFixtureDesc("vertical", "Test Unit").add_children(
|
||||
XBlockFixtureDesc(
|
||||
"discussion",
|
||||
"Test Discussion",
|
||||
metadata={"discussion_id": self.discussion_id}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
).install()
|
||||
|
||||
# create course with single cohort and two content groups (user_partition of type "cohort")
|
||||
self.cohort_name = "OnlyCohort"
|
||||
self.setup_cohort_config(self.course_fixture)
|
||||
self.cohort_id = self.add_manual_cohort(self.course_fixture, self.cohort_name)
|
||||
|
||||
# login as an instructor
|
||||
self.instructor_name = "instructor_user"
|
||||
self.instructor_id = AutoAuthPage(
|
||||
self.browser, username=self.instructor_name, email="instructor_user@example.com",
|
||||
course_id=self.course_id, staff=True
|
||||
).visit().get_user_id()
|
||||
|
||||
# go to the membership page on the instructor dashboard
|
||||
self.instructor_dashboard_page = InstructorDashboardPage(self.browser, self.course_id)
|
||||
self.instructor_dashboard_page.visit()
|
||||
self.discussion_management_page = self.instructor_dashboard_page.select_discussion_management()
|
||||
self.discussion_management_page.wait_for_page()
|
||||
|
||||
self.course_wide_key = 'course-wide'
|
||||
self.inline_key = 'inline'
|
||||
|
||||
def divided_discussion_topics_are_visible(self):
|
||||
"""
|
||||
Assert that discussion topics are visible with appropriate content.
|
||||
"""
|
||||
self.assertTrue(self.discussion_management_page.discussion_topics_visible())
|
||||
|
||||
self.assertEqual(
|
||||
"Course-Wide Discussion Topics",
|
||||
self.discussion_management_page.divided_discussion_heading_is_visible(self.course_wide_key)
|
||||
)
|
||||
self.assertTrue(self.discussion_management_page.is_save_button_disabled(self.course_wide_key))
|
||||
|
||||
self.assertEqual(
|
||||
"Content-Specific Discussion Topics",
|
||||
self.discussion_management_page.divided_discussion_heading_is_visible(self.inline_key)
|
||||
)
|
||||
self.assertTrue(self.discussion_management_page.is_save_button_disabled(self.inline_key))
|
||||
|
||||
def save_and_verify_discussion_topics(self, key):
|
||||
"""
|
||||
Saves the discussion topics and the verify the changes.
|
||||
"""
|
||||
# click on the inline save button.
|
||||
self.discussion_management_page.save_discussion_topics(key)
|
||||
|
||||
# verifies that changes saved successfully.
|
||||
confirmation_message = self.discussion_management_page.get_divide_discussions_message(key=key)
|
||||
self.assertEqual("Your changes have been saved.", confirmation_message)
|
||||
|
||||
# save button disabled again.
|
||||
self.assertTrue(self.discussion_management_page.is_save_button_disabled(key))
|
||||
|
||||
def reload_page(self):
|
||||
"""
|
||||
Refresh the page.
|
||||
"""
|
||||
self.browser.refresh()
|
||||
self.discussion_management_page.wait_for_page()
|
||||
|
||||
self.instructor_dashboard_page.select_discussion_management()
|
||||
self.discussion_management_page.wait_for_page()
|
||||
|
||||
self.divided_discussion_topics_are_visible()
|
||||
|
||||
def verify_discussion_topics_after_reload(self, key, divided_topics):
|
||||
"""
|
||||
Verifies the changed topics.
|
||||
"""
|
||||
self.reload_page()
|
||||
self.assertEqual(self.discussion_management_page.get_divided_topics_count(key), divided_topics)
|
||||
|
||||
def test_divide_course_wide_discussion_topic(self):
|
||||
"""
|
||||
Scenario: divide a course-wide discussion topic.
|
||||
|
||||
Given I have a course with a divide defined,
|
||||
And a course-wide discussion with disabled Save button.
|
||||
When I click on the course-wide discussion topic
|
||||
Then I see the enabled save button
|
||||
When I click on save button
|
||||
Then I see success message
|
||||
When I reload the page
|
||||
Then I see the discussion topic selected
|
||||
"""
|
||||
self.divided_discussion_topics_are_visible()
|
||||
|
||||
divided_topics_before = self.discussion_management_page.get_divided_topics_count(self.course_wide_key)
|
||||
self.discussion_management_page.select_discussion_topic(self.course_wide_key)
|
||||
|
||||
self.assertFalse(self.discussion_management_page.is_save_button_disabled(self.course_wide_key))
|
||||
|
||||
self.save_and_verify_discussion_topics(key=self.course_wide_key)
|
||||
divided_topics_after = self.discussion_management_page.get_divided_topics_count(self.course_wide_key)
|
||||
|
||||
self.assertNotEqual(divided_topics_before, divided_topics_after)
|
||||
|
||||
self.verify_discussion_topics_after_reload(self.course_wide_key, divided_topics_after)
|
||||
|
||||
def test_always_divide_inline_topic_enabled(self):
|
||||
"""
|
||||
Scenario: Select the always_divide_inline_topics radio button
|
||||
|
||||
Given I have a course with a cohort defined,
|
||||
And an inline discussion topic with disabled Save button.
|
||||
When I click on always_divide_inline_topics
|
||||
Then I see enabled save button
|
||||
And I see disabled inline discussion topics
|
||||
When I save the change
|
||||
And I reload the page
|
||||
Then I see the always_divide_inline_topics option enabled
|
||||
"""
|
||||
self.divided_discussion_topics_are_visible()
|
||||
|
||||
# enable always inline discussion topics and save the change
|
||||
self.discussion_management_page.select_always_inline_discussion()
|
||||
self.assertFalse(self.discussion_management_page.is_save_button_disabled(self.inline_key))
|
||||
self.assertTrue(self.discussion_management_page.inline_discussion_topics_disabled())
|
||||
self.discussion_management_page.save_discussion_topics(key=self.inline_key)
|
||||
|
||||
self.reload_page()
|
||||
self.assertTrue(self.discussion_management_page.always_inline_discussion_selected())
|
||||
|
||||
def test_divide_some_inline_topics_enabled(self):
|
||||
"""
|
||||
Scenario: Select the divide_some_inline_topics radio button
|
||||
|
||||
Given I have a course with a divide defined and always_divide_inline_topics set to True
|
||||
And an inline discussion topic with disabled Save button.
|
||||
When I click on divide_some_inline_topics
|
||||
Then I see enabled save button
|
||||
And I see enabled inline discussion topics
|
||||
When I save the change
|
||||
And I reload the page
|
||||
Then I see the divide_some_inline_topics option enabled
|
||||
"""
|
||||
self.divided_discussion_topics_are_visible()
|
||||
# By default always inline discussion topics is False. Enable it (and reload the page).
|
||||
self.assertFalse(self.discussion_management_page.always_inline_discussion_selected())
|
||||
self.discussion_management_page.select_always_inline_discussion()
|
||||
self.discussion_management_page.save_discussion_topics(key=self.inline_key)
|
||||
self.reload_page()
|
||||
self.assertFalse(self.discussion_management_page.divide_some_inline_discussion_selected())
|
||||
|
||||
# enable some inline discussion topic radio button.
|
||||
self.discussion_management_page.select_divide_some_inline_discussion()
|
||||
# I see that save button is enabled
|
||||
self.assertFalse(self.discussion_management_page.is_save_button_disabled(self.inline_key))
|
||||
# I see that inline discussion topics are enabled
|
||||
self.assertFalse(self.discussion_management_page.inline_discussion_topics_disabled())
|
||||
self.discussion_management_page.save_discussion_topics(key=self.inline_key)
|
||||
|
||||
self.reload_page()
|
||||
self.assertTrue(self.discussion_management_page.divide_some_inline_discussion_selected())
|
||||
|
||||
def test_divide_inline_discussion_topic(self):
|
||||
"""
|
||||
Scenario: divide inline discussion topic.
|
||||
|
||||
Given I have a course with a divide defined,
|
||||
And a inline discussion topic with disabled Save button
|
||||
And When I click on inline discussion topic
|
||||
And I see enabled save button
|
||||
And When i click save button
|
||||
Then I see success message
|
||||
When I reload the page
|
||||
Then I see the discussion topic selected
|
||||
"""
|
||||
self.divided_discussion_topics_are_visible()
|
||||
|
||||
divided_topics_before = self.discussion_management_page.get_divided_topics_count(self.inline_key)
|
||||
# check the discussion topic.
|
||||
self.discussion_management_page.select_discussion_topic(self.inline_key)
|
||||
|
||||
# Save button enabled.
|
||||
self.assertFalse(self.discussion_management_page.is_save_button_disabled(self.inline_key))
|
||||
|
||||
# verifies that changes saved successfully.
|
||||
self.save_and_verify_discussion_topics(key=self.inline_key)
|
||||
|
||||
divided_topics_after = self.discussion_management_page.get_divided_topics_count(self.inline_key)
|
||||
self.assertNotEqual(divided_topics_before, divided_topics_after)
|
||||
|
||||
self.verify_discussion_topics_after_reload(self.inline_key, divided_topics_after)
|
||||
|
||||
def test_verify_that_selecting_the_final_child_selects_category(self):
|
||||
"""
|
||||
Scenario: Category should be selected on selecting final child.
|
||||
|
||||
Given I have a course with a cohort defined,
|
||||
And a inline discussion with disabled Save button.
|
||||
When I click on child topics
|
||||
Then I see enabled saved button
|
||||
Then I see parent category to be checked.
|
||||
"""
|
||||
self.divided_discussion_topics_are_visible()
|
||||
|
||||
# category should not be selected.
|
||||
self.assertFalse(self.discussion_management_page.is_category_selected())
|
||||
|
||||
# check the discussion topic.
|
||||
self.discussion_management_page.select_discussion_topic(self.inline_key)
|
||||
|
||||
# verify that category is selected.
|
||||
self.assertTrue(self.discussion_management_page.is_category_selected())
|
||||
|
||||
def test_verify_that_deselecting_the_final_child_deselects_category(self):
|
||||
"""
|
||||
Scenario: Category should be deselected on deselecting final child.
|
||||
|
||||
Given I have a course with a cohort defined,
|
||||
And a inline discussion with disabled Save button.
|
||||
When I click on final child topics
|
||||
Then I see enabled saved button
|
||||
Then I see parent category to be deselected.
|
||||
"""
|
||||
self.divided_discussion_topics_are_visible()
|
||||
|
||||
# category should not be selected.
|
||||
self.assertFalse(self.discussion_management_page.is_category_selected())
|
||||
|
||||
# check the discussion topic.
|
||||
self.discussion_management_page.select_discussion_topic(self.inline_key)
|
||||
|
||||
# verify that category is selected.
|
||||
self.assertTrue(self.discussion_management_page.is_category_selected())
|
||||
|
||||
# un-check the discussion topic.
|
||||
self.discussion_management_page.select_discussion_topic(self.inline_key)
|
||||
|
||||
# category should not be selected.
|
||||
self.assertFalse(self.discussion_management_page.is_category_selected())
|
||||
@@ -26,7 +26,7 @@ from courseware.tests.factories import InstructorFactory
|
||||
from courseware.tabs import get_course_tab_list
|
||||
from openedx.core.djangoapps.course_groups import cohorts
|
||||
from openedx.core.djangoapps.course_groups.cohorts import set_course_cohorted
|
||||
from openedx.core.djangoapps.course_groups.tests.helpers import config_course_cohorts, topic_name_to_id, CohortFactory
|
||||
from openedx.core.djangoapps.course_groups.tests.helpers import config_course_cohorts, config_course_discussions, topic_name_to_id, CohortFactory
|
||||
from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentFactory
|
||||
from openedx.core.djangoapps.content.course_structures.models import CourseStructure
|
||||
from openedx.core.djangoapps.util.testing import ContentGroupTestCase
|
||||
@@ -478,7 +478,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
|
||||
}
|
||||
)
|
||||
|
||||
def test_inline_with_always_cohort_inline_discussion_flag(self):
|
||||
def test_inline_with_always_divide_inline_discussion_flag(self):
|
||||
self.create_discussion("Chapter", "Discussion")
|
||||
set_discussion_division_settings(self.course.id, enable_cohorts=True, always_divide_inline_discussions=True)
|
||||
|
||||
@@ -502,7 +502,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
|
||||
}
|
||||
)
|
||||
|
||||
def test_inline_without_always_cohort_inline_discussion_flag(self):
|
||||
def test_inline_without_always_divide_inline_discussion_flag(self):
|
||||
self.create_discussion("Chapter", "Discussion")
|
||||
set_discussion_division_settings(self.course.id, enable_cohorts=True)
|
||||
|
||||
@@ -1301,15 +1301,16 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
|
||||
)
|
||||
|
||||
# not cohorted
|
||||
config_course_cohorts(course, is_cohorted=False, discussion_topics=["General", "Feedback"])
|
||||
|
||||
config_course_cohorts(course, is_cohorted=False)
|
||||
config_course_discussions(course, discussion_topics=["General", "Feedback"])
|
||||
self.assertFalse(
|
||||
utils.is_commentable_divided(course.id, to_id("General")),
|
||||
"Course isn't cohorted"
|
||||
)
|
||||
|
||||
# cohorted, but top level topics aren't
|
||||
config_course_cohorts(course, is_cohorted=True, discussion_topics=["General", "Feedback"])
|
||||
config_course_cohorts(course, is_cohorted=True)
|
||||
config_course_discussions(course, discussion_topics=["General", "Feedback"])
|
||||
|
||||
self.assertTrue(cohorts.is_course_cohorted(course.id))
|
||||
self.assertFalse(
|
||||
@@ -1320,10 +1321,9 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
|
||||
# cohorted, including "Feedback" top-level topics aren't
|
||||
config_course_cohorts(
|
||||
course,
|
||||
is_cohorted=True,
|
||||
discussion_topics=["General", "Feedback"],
|
||||
divided_discussions=["Feedback"]
|
||||
is_cohorted=True
|
||||
)
|
||||
config_course_discussions(course, discussion_topics=["General", "Feedback"], divided_discussions=["Feedback"])
|
||||
|
||||
self.assertTrue(cohorts.is_course_cohorted(course.id))
|
||||
self.assertFalse(
|
||||
@@ -1345,42 +1345,52 @@ class IsCommentableDividedTestCase(ModuleStoreTestCase):
|
||||
config_course_cohorts(
|
||||
course,
|
||||
is_cohorted=True,
|
||||
)
|
||||
config_course_discussions(
|
||||
course,
|
||||
discussion_topics=["General", "Feedback"],
|
||||
divided_discussions=["Feedback", "random_inline"]
|
||||
)
|
||||
|
||||
self.assertFalse(
|
||||
utils.is_commentable_divided(course.id, to_id("random")),
|
||||
"By default, Non-top-level discussions are not cohorted in a cohorted courses."
|
||||
)
|
||||
|
||||
# if always_cohort_inline_discussions is set to False, non-top-level discussion are always
|
||||
# non cohorted unless they are explicitly set in cohorted_discussions
|
||||
# if always_divide_inline_discussions is set to False, non-top-level discussion are always
|
||||
# not divided unless they are explicitly set in divided_discussions
|
||||
config_course_cohorts(
|
||||
course,
|
||||
is_cohorted=True,
|
||||
)
|
||||
config_course_discussions(
|
||||
course,
|
||||
discussion_topics=["General", "Feedback"],
|
||||
divided_discussions=["Feedback", "random_inline"],
|
||||
always_divide_inline_discussions=False
|
||||
)
|
||||
|
||||
self.assertFalse(
|
||||
utils.is_commentable_divided(course.id, to_id("random")),
|
||||
"Non-top-level discussion is not cohorted if always_cohort_inline_discussions is False."
|
||||
"Non-top-level discussion is not cohorted if always_divide_inline_discussions is False."
|
||||
)
|
||||
self.assertTrue(
|
||||
utils.is_commentable_divided(course.id, to_id("random_inline")),
|
||||
"If always_cohort_inline_discussions set to False, Non-top-level discussion is "
|
||||
"If always_divide_inline_discussions set to False, Non-top-level discussion is "
|
||||
"cohorted if explicitly set in cohorted_discussions."
|
||||
)
|
||||
self.assertTrue(
|
||||
utils.is_commentable_divided(course.id, to_id("Feedback")),
|
||||
"If always_cohort_inline_discussions set to False, top-level discussion are not affected."
|
||||
"If always_divide_inline_discussions set to False, top-level discussion are not affected."
|
||||
)
|
||||
|
||||
def test_is_commentable_divided_team(self):
|
||||
course = modulestore().get_course(self.toy_course_key)
|
||||
self.assertFalse(cohorts.is_course_cohorted(course.id))
|
||||
|
||||
config_course_cohorts(course, is_cohorted=True, always_divide_inline_discussions=True)
|
||||
config_course_cohorts(course, is_cohorted=True)
|
||||
config_course_discussions(course, always_divide_inline_discussions=True)
|
||||
|
||||
team = CourseTeamFactory(course_id=course.id)
|
||||
|
||||
# Verify that team discussions are not cohorted, but other discussions are
|
||||
|
||||
@@ -125,6 +125,7 @@ def instructor_dashboard_2(request, course_id):
|
||||
_section_course_info(course, access),
|
||||
_section_membership(course, access, is_white_label),
|
||||
_section_cohort_management(course, access),
|
||||
_section_discussions_management(course, access),
|
||||
_section_student_admin(course, access),
|
||||
_section_data_download(course, access),
|
||||
]
|
||||
@@ -513,7 +514,6 @@ def _section_cohort_management(course, access):
|
||||
),
|
||||
'cohorts_url': reverse('cohorts', kwargs={'course_key_string': unicode(course_key)}),
|
||||
'upload_cohorts_csv_url': reverse('add_users_to_cohorts', kwargs={'course_id': unicode(course_key)}),
|
||||
'discussion_topics_url': reverse('cohort_discussion_topics', kwargs={'course_key_string': unicode(course_key)}),
|
||||
'verified_track_cohorting_url': reverse(
|
||||
'verified_track_cohorting', kwargs={'course_key_string': unicode(course_key)}
|
||||
),
|
||||
@@ -521,6 +521,21 @@ def _section_cohort_management(course, access):
|
||||
return section_data
|
||||
|
||||
|
||||
def _section_discussions_management(course, access):
|
||||
""" Provide data for the corresponding discussion management section """
|
||||
course_key = course.id
|
||||
section_data = {
|
||||
'section_key': 'discussions_management',
|
||||
'section_display_name': _('Discussions'),
|
||||
'discussion_topics_url': reverse('discussion_topics', kwargs={'course_key_string': unicode(course_key)}),
|
||||
'course_discussion_settings': reverse(
|
||||
'course_discussions_settings',
|
||||
kwargs={'course_key_string': unicode(course_key)}
|
||||
),
|
||||
}
|
||||
return section_data
|
||||
|
||||
|
||||
def _is_small_course(course_key):
|
||||
""" Compares against MAX_ENROLLMENT_INSTR_BUTTONS to determine if course enrollment is considered small. """
|
||||
is_small_course = False
|
||||
|
||||
@@ -1759,6 +1759,7 @@ REQUIRE_JS_PATH_OVERRIDES = {
|
||||
'js/student_profile/views/learner_profile_factory': 'js/student_profile/views/learner_profile_factory.js',
|
||||
'js/courseware/courseware_factory': 'js/courseware/courseware_factory.js',
|
||||
'js/groups/views/cohorts_dashboard_factory': 'js/groups/views/cohorts_dashboard_factory.js',
|
||||
'js/groups/discussions_management/discussions_dashboard_factory': 'js/discussions_management/views/discussions_dashboard_factory.js',
|
||||
'draggabilly': 'js/vendor/draggabilly.js',
|
||||
'hls': 'common/js/vendor/hls.js'
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['backbone'], function(Backbone) {
|
||||
var DiscussionTopicsSettingsModel = Backbone.Model.extend({
|
||||
var CourseDiscussionTopicDetailsModel = Backbone.Model.extend({
|
||||
defaults: {
|
||||
course_wide_discussions: {},
|
||||
inline_discussions: {}
|
||||
}
|
||||
});
|
||||
return DiscussionTopicsSettingsModel;
|
||||
return CourseDiscussionTopicDetailsModel;
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -0,0 +1,14 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['backbone'], function(Backbone) {
|
||||
var CourseDiscussionsSettingsModel = Backbone.Model.extend({
|
||||
idAttribute: 'id',
|
||||
defaults: {
|
||||
divided_inline_discussions: [],
|
||||
divided_course_wide_discussions: [],
|
||||
always_divide_inline_discussions: false
|
||||
}
|
||||
});
|
||||
return CourseDiscussionsSettingsModel;
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
49
lms/static/js/discussions_management/views/discussions.js
Normal file
49
lms/static/js/discussions_management/views/discussions.js
Normal file
@@ -0,0 +1,49 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['jquery', 'underscore', 'backbone', 'gettext',
|
||||
'js/discussions_management/views/divided_discussions_inline',
|
||||
'js/discussions_management/views/divided_discussions_course_wide',
|
||||
'edx-ui-toolkit/js/utils/html-utils'
|
||||
],
|
||||
|
||||
function($, _, Backbone, gettext, InlineDiscussionsView, CourseWideDiscussionsView, HtmlUtils) {
|
||||
var DiscussionsView = Backbone.View.extend({
|
||||
|
||||
initialize: function(options) {
|
||||
this.template = HtmlUtils.template($('#discussions-tpl').text());
|
||||
this.context = options.context;
|
||||
this.discussionSettings = options.discussionSettings;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
HtmlUtils.setHtml(this.$el, this.template({}));
|
||||
this.showDiscussionTopics();
|
||||
return this;
|
||||
},
|
||||
|
||||
getSectionCss: function(section) {
|
||||
return ".instructor-nav .nav-item [data-section='" + section + "']";
|
||||
},
|
||||
|
||||
showDiscussionTopics: function() {
|
||||
var dividedDiscussionsElement = this.$('.discussions-nav');
|
||||
if (!this.CourseWideDiscussionsView) {
|
||||
this.CourseWideDiscussionsView = new CourseWideDiscussionsView({
|
||||
el: dividedDiscussionsElement,
|
||||
model: this.context.courseDiscussionTopicDetailsModel,
|
||||
discussionSettings: this.discussionSettings
|
||||
}).render();
|
||||
}
|
||||
|
||||
if (!this.InlineDiscussionsView) {
|
||||
this.InlineDiscussionsView = new InlineDiscussionsView({
|
||||
el: dividedDiscussionsElement,
|
||||
model: this.context.courseDiscussionTopicDetailsModel,
|
||||
discussionSettings: this.discussionSettings
|
||||
}).render();
|
||||
}
|
||||
}
|
||||
});
|
||||
return DiscussionsView;
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -0,0 +1,33 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define('js/discussions_management/views/discussions_dashboard_factory',
|
||||
['jquery', 'js/discussions_management/views/discussions',
|
||||
'js/discussions_management/models/course_discussions_detail',
|
||||
'js/discussions_management/models/course_discussions_settings'],
|
||||
function($, DiscussionsView, CourseDiscussionTopicDetailsModel, CourseDiscussionsSettingsModel) {
|
||||
return function() {
|
||||
var courseDiscussionSettings = new CourseDiscussionsSettingsModel(),
|
||||
discussionTopicsSettings = new CourseDiscussionTopicDetailsModel(),
|
||||
$discussionsManagementElement = $('.discussions-management'),
|
||||
discussionsView;
|
||||
|
||||
courseDiscussionSettings.url = $discussionsManagementElement.data('course-discussion-settings-url');
|
||||
discussionTopicsSettings.url = $discussionsManagementElement.data('discussion-topics-url');
|
||||
|
||||
discussionsView = new DiscussionsView({
|
||||
el: $discussionsManagementElement,
|
||||
discussionSettings: courseDiscussionSettings,
|
||||
context: {
|
||||
courseDiscussionTopicDetailsModel: discussionTopicsSettings
|
||||
}
|
||||
});
|
||||
|
||||
courseDiscussionSettings.fetch().done(function() {
|
||||
discussionTopicsSettings.fetch().done(function() {
|
||||
discussionsView.render();
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/models/notification', 'js/views/notification'],
|
||||
function($, _, Backbone) {
|
||||
var CohortDiscussionConfigurationView = Backbone.View.extend({
|
||||
function($, _, Backbone, gettext) {
|
||||
/* global NotificationModel, NotificationView */
|
||||
var DividedDiscussionConfigurationView = Backbone.View.extend({
|
||||
|
||||
/**
|
||||
* Add/Remove the disabled attribute on given element.
|
||||
@@ -14,53 +15,53 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the cohorted discussions list.
|
||||
* Returns the divided discussions list.
|
||||
* @param {string} selector - To select the discussion elements whose ids to return.
|
||||
* @returns {Array} - Cohorted discussions.
|
||||
* @returns {Array} - Divided discussions.
|
||||
*/
|
||||
getCohortedDiscussions: function(selector) {
|
||||
getDividedDiscussions: function(selector) {
|
||||
var self = this,
|
||||
cohortedDiscussions = [];
|
||||
dividedDiscussions = [];
|
||||
|
||||
_.each(self.$(selector), function(topic) {
|
||||
cohortedDiscussions.push($(topic).data('id'));
|
||||
dividedDiscussions.push($(topic).data('id'));
|
||||
});
|
||||
return cohortedDiscussions;
|
||||
return dividedDiscussions;
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the cohortSettings' changed attributes to the server via PATCH method.
|
||||
* Save the discussionSettings' changed attributes to the server via PATCH method.
|
||||
* It shows the error message(s) if any.
|
||||
* @param {object} $element - Messages would be shown before this element.
|
||||
* @param {object} fieldData - Data to update on the server.
|
||||
*/
|
||||
saveForm: function($element, fieldData) {
|
||||
var self = this,
|
||||
cohortSettingsModel = this.cohortSettings,
|
||||
discussionSettingsModel = this.discussionSettings,
|
||||
saveOperation = $.Deferred(),
|
||||
showErrorMessage;
|
||||
|
||||
showErrorMessage = function(message, $element) {
|
||||
showErrorMessage = function(message) {
|
||||
self.showMessage(message, $element, 'error');
|
||||
};
|
||||
this.removeNotification();
|
||||
|
||||
cohortSettingsModel.save(
|
||||
discussionSettingsModel.save(
|
||||
fieldData, {patch: true, wait: true}
|
||||
).done(function() {
|
||||
saveOperation.resolve();
|
||||
}).fail(function(result) {
|
||||
var errorMessage = null;
|
||||
var errorMessage = null,
|
||||
jsonResponse;
|
||||
try {
|
||||
var jsonResponse = JSON.parse(result.responseText);
|
||||
jsonResponse = JSON.parse(result.responseText);
|
||||
errorMessage = jsonResponse.error;
|
||||
} catch (e) {
|
||||
// Ignore the exception and show the default error message instead.
|
||||
}
|
||||
if (!errorMessage) {
|
||||
errorMessage = gettext("We've encountered an error. Refresh your browser and then try again.");
|
||||
errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); // eslint-disable-line max-len
|
||||
}
|
||||
showErrorMessage(errorMessage, $element);
|
||||
showErrorMessage(errorMessage);
|
||||
saveOperation.reject();
|
||||
});
|
||||
return saveOperation.promise();
|
||||
@@ -92,6 +93,6 @@
|
||||
}
|
||||
|
||||
});
|
||||
return CohortDiscussionConfigurationView;
|
||||
return DividedDiscussionConfigurationView;
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -1,21 +1,21 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/groups/views/cohort_discussions',
|
||||
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/discussions_management/views/divided_discussions',
|
||||
'edx-ui-toolkit/js/utils/html-utils'],
|
||||
function($, _, Backbone, gettext, CohortDiscussionConfigurationView, HtmlUtils) {
|
||||
var CourseWideDiscussionsView = CohortDiscussionConfigurationView.extend({
|
||||
function($, _, Backbone, gettext, DividedDiscussionConfigurationView, HtmlUtils) {
|
||||
var CourseWideDiscussionsView = DividedDiscussionConfigurationView.extend({
|
||||
events: {
|
||||
'change .check-discussion-subcategory-course-wide': 'discussionCategoryStateChanged',
|
||||
'click .cohort-course-wide-discussions-form .action-save': 'saveCourseWideDiscussionsForm'
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
this.template = HtmlUtils.template($('#cohort-discussions-course-wide-tpl').text());
|
||||
this.cohortSettings = options.cohortSettings;
|
||||
this.template = HtmlUtils.template($('#divided-discussions-course-wide-tpl').text());
|
||||
this.discussionSettings = options.discussionSettings;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
HtmlUtils.setHtml(this.$('.cohort-course-wide-discussions-nav'), this.template({
|
||||
HtmlUtils.setHtml(this.$('.course-wide-discussions-nav'), this.template({
|
||||
courseWideTopicsHtml: this.getCourseWideDiscussionsHtml(
|
||||
this.model.get('course_wide_discussions')
|
||||
)
|
||||
@@ -56,25 +56,27 @@
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends the cohorted_course_wide_discussions to the server and renders the view.
|
||||
* Sends the courseWideDividedDiscussions to the server and renders the view.
|
||||
*/
|
||||
saveCourseWideDiscussionsForm: function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var self = this,
|
||||
courseWideCohortedDiscussions = self.getCohortedDiscussions(
|
||||
courseWideDividedDiscussions = self.getDividedDiscussions(
|
||||
'.check-discussion-subcategory-course-wide:checked'
|
||||
),
|
||||
fieldData = {cohorted_course_wide_discussions: courseWideCohortedDiscussions};
|
||||
fieldData = {divided_course_wide_discussions: courseWideDividedDiscussions};
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
self.saveForm(self.$('.course-wide-discussion-topics'), fieldData)
|
||||
.done(function() {
|
||||
self.model.fetch()
|
||||
.done(function() {
|
||||
self.render();
|
||||
self.showMessage(gettext('Your changes have been saved.'), self.$('.course-wide-discussion-topics'));
|
||||
self.showMessage(gettext('Your changes have been saved.'),
|
||||
self.$('.course-wide-discussion-topics')
|
||||
);
|
||||
}).fail(function() {
|
||||
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again.");
|
||||
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); // eslint-disable-line max-len
|
||||
self.showMessage(errorMessage, self.$('.course-wide-discussion-topics'), 'error');
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/groups/views/cohort_discussions',
|
||||
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/discussions_management/views/divided_discussions',
|
||||
'edx-ui-toolkit/js/utils/html-utils', 'js/vendor/jquery.qubit'],
|
||||
function($, _, Backbone, gettext, CohortDiscussionConfigurationView, HtmlUtils) {
|
||||
var InlineDiscussionsView = CohortDiscussionConfigurationView.extend({
|
||||
function($, _, Backbone, gettext, DividedDiscussionConfigurationView, HtmlUtils) {
|
||||
var InlineDiscussionsView = DividedDiscussionConfigurationView.extend({
|
||||
events: {
|
||||
'change .check-discussion-category': 'setSaveButton',
|
||||
'change .check-discussion-subcategory-inline': 'setSaveButton',
|
||||
@@ -13,17 +13,19 @@
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
this.template = HtmlUtils.template($('#cohort-discussions-inline-tpl').text());
|
||||
this.cohortSettings = options.cohortSettings;
|
||||
this.template = HtmlUtils.template($('#divided-discussions-inline-tpl').text());
|
||||
this.discussionSettings = options.discussionSettings;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var alwaysCohortInlineDiscussions = this.cohortSettings.get('always_cohort_inline_discussions'),
|
||||
inline_discussions = this.model.get('inline_discussions');
|
||||
var inlineDiscussions = this.model.get('inline_discussions'),
|
||||
alwaysDivideInlineDiscussions = this.discussionSettings.get(
|
||||
'always_divide_inline_discussions'
|
||||
);
|
||||
|
||||
HtmlUtils.setHtml(this.$('.cohort-inline-discussions-nav'), this.template({
|
||||
inlineDiscussionTopicsHtml: this.getInlineDiscussionsHtml(inline_discussions),
|
||||
alwaysCohortInlineDiscussions: alwaysCohortInlineDiscussions
|
||||
HtmlUtils.setHtml(this.$('.inline-discussions-nav'), this.template({
|
||||
inlineDiscussionTopicsHtml: this.getInlineDiscussionsHtml(inlineDiscussions),
|
||||
alwaysDivideInlineDiscussions: alwaysDivideInlineDiscussions
|
||||
}));
|
||||
|
||||
// Provides the semantics for a nested list of tri-state checkboxes.
|
||||
@@ -32,7 +34,7 @@
|
||||
// based on the checked values of any checkboxes in child elements of the DOM.
|
||||
this.$('ul.inline-topics').qubit();
|
||||
|
||||
this.setElementsEnabled(alwaysCohortInlineDiscussions, true);
|
||||
this.setElementsEnabled(alwaysDivideInlineDiscussions, true);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -99,45 +101,48 @@
|
||||
*
|
||||
* Enable/Disable the category and sub-category checkboxes.
|
||||
* Enable/Disable the save button.
|
||||
* @param {bool} enable_checkboxes - The flag to enable/disable the checkboxes.
|
||||
* @param {bool} enable_save_button - The flag to enable/disable the save button.
|
||||
* @param {bool} enableCheckboxes - The flag to enable/disable the checkboxes.
|
||||
* @param {bool} enableSaveButton - The flag to enable/disable the save button.
|
||||
*/
|
||||
setElementsEnabled: function(enable_checkboxes, enable_save_button) {
|
||||
this.setDisabled(this.$('.check-discussion-category'), enable_checkboxes);
|
||||
this.setDisabled(this.$('.check-discussion-subcategory-inline'), enable_checkboxes);
|
||||
this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), enable_save_button);
|
||||
setElementsEnabled: function(enableCheckboxes, enableSaveButton) {
|
||||
this.setDisabled(this.$('.check-discussion-category'), enableCheckboxes);
|
||||
this.setDisabled(this.$('.check-discussion-subcategory-inline'), enableCheckboxes);
|
||||
this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), enableSaveButton);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables the save button for inline discussions.
|
||||
*/
|
||||
setSaveButton: function(event) {
|
||||
setSaveButton: function() {
|
||||
this.setDisabled(this.$('.cohort-inline-discussions-form .action-save'), false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends the cohorted_inline_discussions to the server and renders the view.
|
||||
* Sends the dividedInlineDiscussions to the server and renders the view.
|
||||
*/
|
||||
saveInlineDiscussionsForm: function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var self = this,
|
||||
cohortedInlineDiscussions = self.getCohortedDiscussions(
|
||||
dividedInlineDiscussions = self.getDividedDiscussions(
|
||||
'.check-discussion-subcategory-inline:checked'
|
||||
),
|
||||
fieldData = {
|
||||
cohorted_inline_discussions: cohortedInlineDiscussions,
|
||||
always_cohort_inline_discussions: self.$('.check-all-inline-discussions').prop('checked')
|
||||
divided_inline_discussions: dividedInlineDiscussions,
|
||||
always_divide_inline_discussions: self.$(
|
||||
'.check-all-inline-discussions'
|
||||
).prop('checked')
|
||||
};
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
self.saveForm(self.$('.inline-discussion-topics'), fieldData)
|
||||
.done(function() {
|
||||
self.model.fetch()
|
||||
.done(function() {
|
||||
self.render();
|
||||
self.showMessage(gettext('Your changes have been saved.'), self.$('.inline-discussion-topics'));
|
||||
self.showMessage(gettext('Your changes have been saved.'),
|
||||
self.$('.inline-discussion-topics'));
|
||||
}).fail(function() {
|
||||
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again.");
|
||||
var errorMessage = gettext("We've encountered an error. Refresh your browser and then try again."); // eslint-disable-line max-len
|
||||
self.showMessage(errorMessage, self.$('.inline-discussion-topics'), 'error');
|
||||
});
|
||||
});
|
||||
@@ -4,10 +4,7 @@
|
||||
var CourseCohortSettingsModel = Backbone.Model.extend({
|
||||
idAttribute: 'id',
|
||||
defaults: {
|
||||
is_cohorted: false,
|
||||
cohorted_inline_discussions: [],
|
||||
cohorted_course_wide_discussions: [],
|
||||
always_cohort_inline_discussions: false
|
||||
is_cohorted: false
|
||||
}
|
||||
});
|
||||
return CourseCohortSettingsModel;
|
||||
|
||||
@@ -4,13 +4,11 @@
|
||||
'js/groups/models/verified_track_settings',
|
||||
'js/groups/views/cohort_editor', 'js/groups/views/cohort_form',
|
||||
'js/groups/views/course_cohort_settings_notification',
|
||||
'js/groups/views/cohort_discussions_inline', 'js/groups/views/cohort_discussions_course_wide',
|
||||
'js/groups/views/verified_track_settings_notification',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'js/views/file_uploader', 'js/models/notification', 'js/views/notification', 'string_utils'],
|
||||
function($, _, Backbone, gettext, CohortModel, VerifiedTrackSettingsModel, CohortEditorView, CohortFormView,
|
||||
CourseCohortSettingsNotificationView, InlineDiscussionsView, CourseWideDiscussionsView,
|
||||
VerifiedTrackSettingsNotificationView, HtmlUtils) {
|
||||
CourseCohortSettingsNotificationView, VerifiedTrackSettingsNotificationView, HtmlUtils) {
|
||||
var hiddenClass = 'is-hidden',
|
||||
disabledClass = 'is-disabled',
|
||||
enableCohortsSelector = '.cohorts-state';
|
||||
@@ -25,7 +23,6 @@
|
||||
'click .cohort-management-add-form .action-cancel': 'cancelAddCohortForm',
|
||||
'click .link-cross-reference': 'showSection',
|
||||
'click .toggle-cohort-management-secondary': 'showCsvUpload',
|
||||
'click .toggle-cohort-management-discussions': 'showDiscussionTopics'
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
@@ -306,27 +303,6 @@
|
||||
}).render();
|
||||
}
|
||||
},
|
||||
showDiscussionTopics: function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
$(event.currentTarget).addClass(hiddenClass);
|
||||
var cohortDiscussionsElement = this.$('.cohort-discussions-nav').removeClass(hiddenClass);
|
||||
|
||||
if (!this.CourseWideDiscussionsView) {
|
||||
this.CourseWideDiscussionsView = new CourseWideDiscussionsView({
|
||||
el: cohortDiscussionsElement,
|
||||
model: this.context.discussionTopicsSettingsModel,
|
||||
cohortSettings: this.cohortSettings
|
||||
}).render();
|
||||
}
|
||||
if (!this.InlineDiscussionsView) {
|
||||
this.InlineDiscussionsView = new InlineDiscussionsView({
|
||||
el: cohortDiscussionsElement,
|
||||
model: this.context.discussionTopicsSettingsModel,
|
||||
cohortSettings: this.cohortSettings
|
||||
}).render();
|
||||
}
|
||||
},
|
||||
|
||||
getSectionCss: function(section) {
|
||||
return ".instructor-nav .nav-item [data-section='" + section + "']";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
(function(define, undefined) {
|
||||
'use strict';
|
||||
define(['jquery', 'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/course_cohort_settings',
|
||||
'js/groups/models/cohort_discussions', 'js/groups/models/content_group'],
|
||||
function($, CohortsView, CohortCollection, CourseCohortSettingsModel, DiscussionTopicsSettingsModel, ContentGroupModel) {
|
||||
'js/groups/models/content_group'],
|
||||
function($, CohortsView, CohortCollection, CourseCohortSettingsModel, ContentGroupModel) {
|
||||
return function(contentGroups, studioGroupConfigurationsUrl) {
|
||||
var contentGroupModels = $.map(contentGroups, function(group) {
|
||||
return new ContentGroupModel({
|
||||
@@ -14,33 +14,27 @@
|
||||
|
||||
var cohorts = new CohortCollection(),
|
||||
courseCohortSettings = new CourseCohortSettingsModel(),
|
||||
discussionTopicsSettings = new DiscussionTopicsSettingsModel();
|
||||
$cohortManagementElement = $('.cohort-management');
|
||||
|
||||
var cohortManagementElement = $('.cohort-management');
|
||||
|
||||
cohorts.url = cohortManagementElement.data('cohorts_url');
|
||||
courseCohortSettings.url = cohortManagementElement.data('course_cohort_settings_url');
|
||||
discussionTopicsSettings.url = cohortManagementElement.data('discussion-topics-url');
|
||||
cohorts.url = $cohortManagementElement.data('cohorts_url');
|
||||
courseCohortSettings.url = $cohortManagementElement.data('course_cohort_settings_url');
|
||||
|
||||
var cohortsView = new CohortsView({
|
||||
el: cohortManagementElement,
|
||||
el: $cohortManagementElement,
|
||||
model: cohorts,
|
||||
contentGroups: contentGroupModels,
|
||||
cohortSettings: courseCohortSettings,
|
||||
context: {
|
||||
discussionTopicsSettingsModel: discussionTopicsSettings,
|
||||
uploadCohortsCsvUrl: cohortManagementElement.data('upload_cohorts_csv_url'),
|
||||
verifiedTrackCohortingUrl: cohortManagementElement.data('verified_track_cohorting_url'),
|
||||
uploadCohortsCsvUrl: $cohortManagementElement.data('upload_cohorts_csv_url'),
|
||||
verifiedTrackCohortingUrl: $cohortManagementElement.data('verified_track_cohorting_url'),
|
||||
studioGroupConfigurationsUrl: studioGroupConfigurationsUrl,
|
||||
isCcxEnabled: cohortManagementElement.data('is_ccx_enabled')
|
||||
isCcxEnabled: $cohortManagementElement.data('is_ccx_enabled')
|
||||
}
|
||||
});
|
||||
|
||||
cohorts.fetch().done(function() {
|
||||
courseCohortSettings.fetch().done(function() {
|
||||
discussionTopicsSettings.fetch().done(function() {
|
||||
cohortsView.render();
|
||||
});
|
||||
cohortsView.render();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
12
lms/static/js/instructor_dashboard/discussions_management.js
Normal file
12
lms/static/js/instructor_dashboard/discussions_management.js
Normal file
@@ -0,0 +1,12 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
function DiscussionsManagement($section) {
|
||||
this.$section = $section;
|
||||
this.$section.data('wrapper', this);
|
||||
}
|
||||
|
||||
DiscussionsManagement.prototype.onClickTitle = function() {};
|
||||
|
||||
window.InstructorDashboard.sections.DiscussionsManagement = DiscussionsManagement;
|
||||
}).call(this);
|
||||
@@ -187,6 +187,9 @@ such that the value can be defined later than this assignment (file load order).
|
||||
}, {
|
||||
constructor: window.InstructorDashboard.sections.CohortManagement,
|
||||
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#cohort_management')
|
||||
}, {
|
||||
constructor: window.InstructorDashboard.sections.DiscussionsManagement,
|
||||
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#discussions_management')
|
||||
}, {
|
||||
constructor: window.InstructorDashboard.sections.Certificates,
|
||||
$element: idashContent.find('.' + CSS_IDASH_SECTION + '#certificates')
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
|
||||
'common/js/spec_helpers/template_helpers',
|
||||
'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/content_group',
|
||||
'js/groups/models/course_cohort_settings', 'js/utils/animation', 'js/vendor/jquery.qubit',
|
||||
'js/groups/views/course_cohort_settings_notification', 'js/groups/models/cohort_discussions',
|
||||
'js/groups/views/cohort_discussions', 'js/groups/views/cohort_discussions_course_wide',
|
||||
'js/groups/views/cohort_discussions_inline'
|
||||
],
|
||||
'common/js/spec_helpers/template_helpers',
|
||||
'js/groups/views/cohorts', 'js/groups/collections/cohort', 'js/groups/models/content_group',
|
||||
'js/groups/models/course_cohort_settings', 'js/utils/animation', 'js/vendor/jquery.qubit',
|
||||
'js/groups/views/course_cohort_settings_notification'
|
||||
],
|
||||
function(Backbone, $, AjaxHelpers, TemplateHelpers, CohortsView, CohortCollection, ContentGroupModel,
|
||||
CourseCohortSettingsModel, AnimationUtil, Qubit, CourseCohortSettingsNotificationView, DiscussionTopicsSettingsModel,
|
||||
CohortDiscussionsView, CohortCourseWideDiscussionsView, CohortInlineDiscussionsView) {
|
||||
CourseCohortSettingsModel, AnimationUtil, Qubit, CourseCohortSettingsNotificationView) {
|
||||
'use strict';
|
||||
|
||||
describe('Cohorts View', function() {
|
||||
@@ -20,16 +17,7 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
|
||||
getAddModal, selectContentGroup, clearContentGroup,
|
||||
saveFormAndExpectErrors, createMockCohortSettings, MOCK_COHORTED_USER_PARTITION_ID,
|
||||
MOCK_UPLOAD_COHORTS_CSV_URL, MOCK_STUDIO_ADVANCED_SETTINGS_URL, MOCK_STUDIO_GROUP_CONFIGURATIONS_URL,
|
||||
MOCK_VERIFIED_TRACK_COHORTING_URL, MOCK_MANUAL_ASSIGNMENT, MOCK_RANDOM_ASSIGNMENT,
|
||||
createMockCohortDiscussionsJson, createMockCohortDiscussions, showAndAssertDiscussionTopics;
|
||||
|
||||
// Selectors
|
||||
var discussionsToggle = '.toggle-cohort-management-discussions',
|
||||
inlineDiscussionsFormCss = '.cohort-inline-discussions-form',
|
||||
courseWideDiscussionsFormCss = '.cohort-course-wide-discussions-form',
|
||||
courseWideDiscussionsSaveButtonCss = '.cohort-course-wide-discussions-form .action-save',
|
||||
inlineDiscussionsSaveButtonCss = '.cohort-inline-discussions-form .action-save',
|
||||
inlineDiscussionsForm, courseWideDiscussionsForm;
|
||||
MOCK_VERIFIED_TRACK_COHORTING_URL, MOCK_MANUAL_ASSIGNMENT, MOCK_RANDOM_ASSIGNMENT;
|
||||
|
||||
MOCK_MANUAL_ASSIGNMENT = 'manual';
|
||||
MOCK_RANDOM_ASSIGNMENT = 'random';
|
||||
@@ -70,66 +58,16 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
|
||||
];
|
||||
};
|
||||
|
||||
createMockCohortSettingsJson = function(isCohorted, cohortedInlineDiscussions, cohortedCourseWideDiscussions, alwaysCohortInlineDiscussions) {
|
||||
createMockCohortSettingsJson = function(isCohorted) {
|
||||
return {
|
||||
id: 0,
|
||||
is_cohorted: isCohorted || false,
|
||||
cohorted_inline_discussions: cohortedInlineDiscussions || [],
|
||||
cohorted_course_wide_discussions: cohortedCourseWideDiscussions || [],
|
||||
always_cohort_inline_discussions: alwaysCohortInlineDiscussions || false
|
||||
is_cohorted: isCohorted || false
|
||||
};
|
||||
};
|
||||
|
||||
createMockCohortSettings = function(isCohorted, cohortedInlineDiscussions, cohortedCourseWideDiscussions, alwaysCohortInlineDiscussions) {
|
||||
createMockCohortSettings = function(isCohorted) {
|
||||
return new CourseCohortSettingsModel(
|
||||
createMockCohortSettingsJson(isCohorted, cohortedInlineDiscussions, cohortedCourseWideDiscussions, alwaysCohortInlineDiscussions)
|
||||
);
|
||||
};
|
||||
|
||||
createMockCohortDiscussionsJson = function(allCohorted) {
|
||||
return {
|
||||
course_wide_discussions: {
|
||||
children: [['Topic_C_1', 'entry'], ['Topic_C_2', 'entry']],
|
||||
entries: {
|
||||
Topic_C_1: {
|
||||
sort_key: null,
|
||||
is_divided: true,
|
||||
id: 'Topic_C_1'
|
||||
},
|
||||
Topic_C_2: {
|
||||
sort_key: null,
|
||||
is_divided: false,
|
||||
id: 'Topic_C_2'
|
||||
}
|
||||
}
|
||||
},
|
||||
inline_discussions: {
|
||||
subcategories: {
|
||||
Topic_I_1: {
|
||||
subcategories: {},
|
||||
children: [['Inline_Discussion_1', 'entry'], ['Inline_Discussion_2', 'entry']],
|
||||
entries: {
|
||||
Inline_Discussion_1: {
|
||||
sort_key: null,
|
||||
is_divided: true,
|
||||
id: 'Inline_Discussion_1'
|
||||
},
|
||||
Inline_Discussion_2: {
|
||||
sort_key: null,
|
||||
is_divided: allCohorted || false,
|
||||
id: 'Inline_Discussion_2'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
children: [['Topic_I_1', 'subcategory']]
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
createMockCohortDiscussions = function(allCohorted) {
|
||||
return new DiscussionTopicsSettingsModel(
|
||||
createMockCohortDiscussionsJson(allCohorted)
|
||||
createMockCohortSettingsJson(isCohorted)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -146,7 +84,7 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
|
||||
};
|
||||
|
||||
createCohortsView = function(test, options) {
|
||||
var cohortsJson, cohorts, contentGroups, cohortSettings, cohortDiscussions;
|
||||
var cohortsJson, cohorts, contentGroups, cohortSettings;
|
||||
options = options || {};
|
||||
cohortsJson = options.cohorts ? {cohorts: options.cohorts} : createMockCohorts();
|
||||
cohorts = new CohortCollection(cohortsJson, {parse: true});
|
||||
@@ -155,16 +93,12 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
|
||||
cohortSettings.url = '/mock_service/cohorts/settings';
|
||||
cohorts.url = '/mock_service/cohorts';
|
||||
|
||||
cohortDiscussions = options.cohortDiscussions || createMockCohortDiscussions();
|
||||
cohortDiscussions.url = '/mock_service/cohorts/discussion/topics';
|
||||
|
||||
requests = AjaxHelpers.requests(test);
|
||||
cohortsView = new CohortsView({
|
||||
model: cohorts,
|
||||
contentGroups: contentGroups,
|
||||
cohortSettings: cohortSettings,
|
||||
context: {
|
||||
discussionTopicsSettingsModel: cohortDiscussions,
|
||||
uploadCohortsCsvUrl: MOCK_UPLOAD_COHORTS_CSV_URL,
|
||||
studioAdvancedSettingsUrl: MOCK_STUDIO_ADVANCED_SETTINGS_URL,
|
||||
studioGroupConfigurationsUrl: MOCK_STUDIO_GROUP_CONFIGURATIONS_URL,
|
||||
@@ -314,40 +248,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
|
||||
verifyDetailedMessage(expectedTitle, 'error', errors);
|
||||
};
|
||||
|
||||
showAndAssertDiscussionTopics = function(that) {
|
||||
createCohortsView(that);
|
||||
|
||||
// Should see the control to toggle cohort discussions.
|
||||
expect(cohortsView.$(discussionsToggle)).not.toHaveClass('is-hidden');
|
||||
// But discussions form should not be visible until toggle is clicked.
|
||||
expect(cohortsView.$(inlineDiscussionsFormCss).length).toBe(0);
|
||||
expect(cohortsView.$(courseWideDiscussionsFormCss).length).toBe(0);
|
||||
|
||||
expect(cohortsView.$(discussionsToggle).text()).
|
||||
toContain('Specify whether discussion topics are divided by cohort');
|
||||
|
||||
cohortsView.$(discussionsToggle).click();
|
||||
// After toggle is clicked, it should be hidden.
|
||||
expect(cohortsView.$(discussionsToggle)).toHaveClass('is-hidden');
|
||||
|
||||
// Should see the course wide discussions form and its content
|
||||
courseWideDiscussionsForm = cohortsView.$(courseWideDiscussionsFormCss);
|
||||
expect(courseWideDiscussionsForm.length).toBe(1);
|
||||
|
||||
expect(courseWideDiscussionsForm.text()).
|
||||
toContain('Course-Wide Discussion Topics');
|
||||
expect(courseWideDiscussionsForm.text()).
|
||||
toContain('Select the course-wide discussion topics that you want to divide by cohort.');
|
||||
|
||||
// Should see the inline discussions form and its content
|
||||
inlineDiscussionsForm = cohortsView.$(inlineDiscussionsFormCss);
|
||||
expect(inlineDiscussionsForm.length).toBe(1);
|
||||
expect(inlineDiscussionsForm.text()).
|
||||
toContain('Content-Specific Discussion Topics');
|
||||
expect(inlineDiscussionsForm.text()).
|
||||
toContain('Specify whether content-specific discussion topics are divided by cohort.');
|
||||
};
|
||||
|
||||
unknownUserMessage = function(name) {
|
||||
return 'Unknown user: ' + name;
|
||||
};
|
||||
@@ -364,10 +264,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
|
||||
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-group-header');
|
||||
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/notification');
|
||||
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-state');
|
||||
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-discussions-category');
|
||||
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-discussions-subcategory');
|
||||
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-discussions-course-wide');
|
||||
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/cohort-discussions-inline');
|
||||
TemplateHelpers.installTemplate('templates/file-upload');
|
||||
});
|
||||
|
||||
@@ -381,8 +277,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
|
||||
|
||||
// If no cohorts have been created, can't upload a CSV file.
|
||||
expect(cohortsView.$('.wrapper-cohort-supplemental')).toHaveClass('is-hidden');
|
||||
// if no cohorts have been created, can't show the link to discussion topics.
|
||||
expect(cohortsView.$('.cohort-discussions-nav')).toHaveClass('is-hidden');
|
||||
});
|
||||
|
||||
it('syncs data when membership tab is clicked', function() {
|
||||
@@ -422,10 +316,6 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
|
||||
.toBe("Your file 'upload_file.txt' has been uploaded. Allow a few minutes for processing.");
|
||||
});
|
||||
|
||||
it('can show discussion topics if cohort exists', function() {
|
||||
showAndAssertDiscussionTopics(this);
|
||||
});
|
||||
|
||||
describe('Cohort Selector', function() {
|
||||
it('has no initial selection', function() {
|
||||
createCohortsView(this);
|
||||
@@ -1172,375 +1062,5 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Discussion Topics', function() {
|
||||
var createCourseWideView, createInlineView,
|
||||
inlineView, courseWideView, assertCohortedTopics;
|
||||
|
||||
createCourseWideView = function(that) {
|
||||
createCohortsView(that);
|
||||
|
||||
courseWideView = new CohortCourseWideDiscussionsView({
|
||||
el: cohortsView.$('.cohort-discussions-nav').removeClass('is-hidden'),
|
||||
model: cohortsView.context.discussionTopicsSettingsModel,
|
||||
cohortSettings: cohortsView.cohortSettings
|
||||
});
|
||||
courseWideView.render();
|
||||
};
|
||||
|
||||
createInlineView = function(that, discussionTopicsSettingsModel) {
|
||||
createCohortsView(that);
|
||||
|
||||
inlineView = new CohortInlineDiscussionsView({
|
||||
el: cohortsView.$('.cohort-discussions-nav').removeClass('is-hidden'),
|
||||
model: discussionTopicsSettingsModel || cohortsView.context.discussionTopicsSettingsModel,
|
||||
cohortSettings: cohortsView.cohortSettings
|
||||
});
|
||||
inlineView.render();
|
||||
};
|
||||
|
||||
assertCohortedTopics = function(view, type) {
|
||||
expect(view.$('.check-discussion-subcategory-' + type).length).toBe(2);
|
||||
expect(view.$('.check-discussion-subcategory-' + type + ':checked').length).toBe(1);
|
||||
};
|
||||
|
||||
it('renders the view properly', function() {
|
||||
showAndAssertDiscussionTopics(this);
|
||||
});
|
||||
|
||||
describe('Course Wide', function() {
|
||||
it('shows the "Save" button as disabled initially', function() {
|
||||
createCourseWideView(this);
|
||||
expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('has one cohorted and one non-cohorted topic', function() {
|
||||
createCourseWideView(this);
|
||||
|
||||
assertCohortedTopics(courseWideView, 'course-wide');
|
||||
|
||||
expect(courseWideView.$('.cohorted-text').length).toBe(2);
|
||||
expect(courseWideView.$('.cohorted-text.hidden').length).toBe(1);
|
||||
});
|
||||
|
||||
it('enables the "Save" button after changing checkbox', function() {
|
||||
createCourseWideView(this);
|
||||
|
||||
// save button is disabled.
|
||||
expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
|
||||
|
||||
$(courseWideView.$('.check-discussion-subcategory-course-wide')[0]).prop('checked', false).change();
|
||||
|
||||
// save button is enabled.
|
||||
expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('saves the topic successfully', function() {
|
||||
createCourseWideView(this);
|
||||
|
||||
$(courseWideView.$('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change();
|
||||
expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
|
||||
|
||||
// Save the updated settings
|
||||
courseWideView.$('.action-save').click();
|
||||
|
||||
// fake requests for cohort settings with PATCH method.
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'PATCH', '/mock_service/cohorts/settings',
|
||||
{cohorted_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
|
||||
);
|
||||
AjaxHelpers.respondWithJson(
|
||||
requests,
|
||||
{cohorted_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
|
||||
);
|
||||
|
||||
// fake request for discussion/topics with GET method.
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'GET', '/mock_service/cohorts/discussion/topics'
|
||||
);
|
||||
AjaxHelpers.respondWithJson(
|
||||
requests,
|
||||
createMockCohortDiscussions()
|
||||
);
|
||||
|
||||
// verify the success message.
|
||||
expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
|
||||
verifyMessage('Your changes have been saved.', 'confirmation');
|
||||
});
|
||||
|
||||
it('shows an appropriate message when subsequent "GET" returns HTTP500', function() {
|
||||
createCourseWideView(this);
|
||||
|
||||
$(courseWideView.$('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change();
|
||||
expect(courseWideView.$(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
|
||||
|
||||
// Save the updated settings
|
||||
courseWideView.$('.action-save').click();
|
||||
|
||||
// fake requests for cohort settings with PATCH method.
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'PATCH', '/mock_service/cohorts/settings',
|
||||
{cohorted_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
|
||||
);
|
||||
AjaxHelpers.respondWithJson(
|
||||
requests,
|
||||
{cohorted_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
|
||||
);
|
||||
|
||||
// fake request for discussion/topics with GET method.
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'GET', '/mock_service/cohorts/discussion/topics'
|
||||
);
|
||||
AjaxHelpers.respondWithError(requests, 500);
|
||||
|
||||
var expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
|
||||
expect(courseWideView.$('.message-title').text().trim()).toBe(expectedTitle);
|
||||
});
|
||||
|
||||
it('shows an appropriate error message for HTTP500', function() {
|
||||
createCourseWideView(this);
|
||||
|
||||
$(courseWideView.$('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change();
|
||||
courseWideView.$('.action-save').click();
|
||||
|
||||
AjaxHelpers.respondWithError(requests, 500);
|
||||
var expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
|
||||
expect(courseWideView.$('.message-title').text().trim()).toBe(expectedTitle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Inline', function() {
|
||||
var enableSaveButton, mockGetRequest, verifySuccess, mockPatchRequest;
|
||||
|
||||
enableSaveButton = function() {
|
||||
// enable the inline discussion topics.
|
||||
inlineView.$('.check-cohort-inline-discussions').prop('checked', 'checked').change();
|
||||
|
||||
$(inlineView.$('.check-discussion-subcategory-inline')[0]).prop('checked', 'checked').change();
|
||||
|
||||
expect(inlineView.$(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
|
||||
};
|
||||
|
||||
verifySuccess = function() {
|
||||
// verify the success message.
|
||||
expect(inlineView.$(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
|
||||
verifyMessage('Your changes have been saved.', 'confirmation');
|
||||
};
|
||||
|
||||
mockPatchRequest = function(cohortedInlineDiscussions) {
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'PATCH', '/mock_service/cohorts/settings',
|
||||
{
|
||||
cohorted_inline_discussions: cohortedInlineDiscussions,
|
||||
always_cohort_inline_discussions: false
|
||||
}
|
||||
);
|
||||
AjaxHelpers.respondWithJson(
|
||||
requests,
|
||||
{
|
||||
cohorted_inline_discussions: cohortedInlineDiscussions,
|
||||
always_cohort_inline_discussions: false
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
mockGetRequest = function(allCohorted) {
|
||||
// fake request for discussion/topics with GET method.
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'GET', '/mock_service/cohorts/discussion/topics'
|
||||
);
|
||||
AjaxHelpers.respondWithJson(
|
||||
requests,
|
||||
createMockCohortDiscussions(allCohorted)
|
||||
);
|
||||
};
|
||||
|
||||
it('shows the "Save" button as disabled initially', function() {
|
||||
createInlineView(this);
|
||||
expect(inlineView.$(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows always cohort radio button as selected', function() {
|
||||
createInlineView(this);
|
||||
inlineView.$('.check-all-inline-discussions').prop('checked', 'checked').change();
|
||||
|
||||
// verify always cohort inline discussions is being selected.
|
||||
expect(inlineView.$('.check-all-inline-discussions').prop('checked')).toBeTruthy();
|
||||
|
||||
// verify that inline topics are disabled
|
||||
expect(inlineView.$('.check-discussion-subcategory-inline').prop('disabled')).toBeTruthy();
|
||||
expect(inlineView.$('.check-discussion-category').prop('disabled')).toBeTruthy();
|
||||
|
||||
// verify that cohort some topics are not being selected.
|
||||
expect(inlineView.$('.check-cohort-inline-discussions').prop('checked')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('shows cohort some topics radio button as selected', function() {
|
||||
createInlineView(this);
|
||||
inlineView.$('.check-cohort-inline-discussions').prop('checked', 'checked').change();
|
||||
|
||||
// verify some cohort inline discussions radio is being selected.
|
||||
expect(inlineView.$('.check-cohort-inline-discussions').prop('checked')).toBeTruthy();
|
||||
|
||||
// verify always cohort radio is not selected.
|
||||
expect(inlineView.$('.check-all-inline-discussions').prop('checked')).toBeFalsy();
|
||||
|
||||
// verify that inline topics are enabled
|
||||
expect(inlineView.$('.check-discussion-subcategory-inline').prop('disabled')).toBeFalsy();
|
||||
expect(inlineView.$('.check-discussion-category').prop('disabled')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('has cohorted and non-cohorted topics', function() {
|
||||
createInlineView(this);
|
||||
enableSaveButton();
|
||||
assertCohortedTopics(inlineView, 'inline');
|
||||
});
|
||||
|
||||
it('enables "Save" button after changing from always inline option', function() {
|
||||
createInlineView(this);
|
||||
enableSaveButton();
|
||||
});
|
||||
|
||||
it('saves the topic', function() {
|
||||
createInlineView(this);
|
||||
enableSaveButton();
|
||||
|
||||
// Save the updated settings
|
||||
inlineView.$('.action-save').click();
|
||||
|
||||
mockPatchRequest(['Inline_Discussion_1']);
|
||||
mockGetRequest();
|
||||
|
||||
verifySuccess();
|
||||
});
|
||||
|
||||
it('selects the parent category when all children are selected', function() {
|
||||
createInlineView(this);
|
||||
enableSaveButton();
|
||||
|
||||
// parent category should be indeterminate.
|
||||
expect(inlineView.$('.check-discussion-category:checked').length).toBe(0);
|
||||
expect(inlineView.$('.check-discussion-category:indeterminate').length).toBe(1);
|
||||
|
||||
inlineView.$('.check-discussion-subcategory-inline').prop('checked', 'checked').change();
|
||||
// parent should be checked as we checked all children
|
||||
expect(inlineView.$('.check-discussion-category:checked').length).toBe(1);
|
||||
});
|
||||
|
||||
it('selects/deselects all children when a parent category is selected/deselected', function() {
|
||||
createInlineView(this);
|
||||
enableSaveButton();
|
||||
|
||||
expect(inlineView.$('.check-discussion-category:checked').length).toBe(0);
|
||||
|
||||
inlineView.$('.check-discussion-category').prop('checked', 'checked').change();
|
||||
|
||||
expect(inlineView.$('.check-discussion-category:checked').length).toBe(1);
|
||||
expect(inlineView.$('.check-discussion-subcategory-inline:checked').length).toBe(2);
|
||||
|
||||
// un-check the parent, all children should be unchecd.
|
||||
inlineView.$('.check-discussion-category').prop('checked', false).change();
|
||||
expect(inlineView.$('.check-discussion-category:checked').length).toBe(0);
|
||||
expect(inlineView.$('.check-discussion-subcategory-inline:checked').length).toBe(0);
|
||||
});
|
||||
|
||||
it('saves correctly when a subset of topics are selected within a category', function() {
|
||||
createInlineView(this);
|
||||
enableSaveButton();
|
||||
|
||||
// parent category should be indeterminate.
|
||||
expect(inlineView.$('.check-discussion-category:checked').length).toBe(0);
|
||||
expect(inlineView.$('.check-discussion-category:indeterminate').length).toBe(1);
|
||||
|
||||
// Save the updated settings
|
||||
inlineView.$('.action-save').click();
|
||||
|
||||
mockPatchRequest(['Inline_Discussion_1']);
|
||||
mockGetRequest();
|
||||
|
||||
verifySuccess();
|
||||
// parent category should be indeterminate.
|
||||
expect(inlineView.$('.check-discussion-category:indeterminate').length).toBe(1);
|
||||
});
|
||||
|
||||
it('saves correctly when all child topics are selected within a category', function() {
|
||||
createInlineView(this);
|
||||
enableSaveButton();
|
||||
|
||||
// parent category should be indeterminate.
|
||||
expect(inlineView.$('.check-discussion-category:checked').length).toBe(0);
|
||||
expect(inlineView.$('.check-discussion-category:indeterminate').length).toBe(1);
|
||||
|
||||
inlineView.$('.check-discussion-subcategory-inline').prop('checked', 'checked').change();
|
||||
// Save the updated settings
|
||||
inlineView.$('.action-save').click();
|
||||
|
||||
mockPatchRequest(['Inline_Discussion_1', 'Inline_Discussion_2']);
|
||||
mockGetRequest(true);
|
||||
|
||||
verifySuccess();
|
||||
// parent category should be checked.
|
||||
expect(inlineView.$('.check-discussion-category:checked').length).toBe(1);
|
||||
});
|
||||
|
||||
it('shows an appropriate message when no inline topics exist', function() {
|
||||
var topicsJson, discussionTopicsSettingsModel;
|
||||
|
||||
topicsJson = {
|
||||
course_wide_discussions: {
|
||||
children: [['Topic_C_1', 'entry']],
|
||||
entries: {
|
||||
Topic_C_1: {
|
||||
sort_key: null,
|
||||
is_divided: true,
|
||||
id: 'Topic_C_1'
|
||||
}
|
||||
}
|
||||
},
|
||||
inline_discussions: {
|
||||
subcategories: {},
|
||||
children: []
|
||||
}
|
||||
};
|
||||
discussionTopicsSettingsModel = new DiscussionTopicsSettingsModel(topicsJson);
|
||||
|
||||
createInlineView(this, discussionTopicsSettingsModel);
|
||||
|
||||
var expectedTitle = 'No content-specific discussion topics exist.';
|
||||
expect(inlineView.$('.no-topics').text().trim()).toBe(expectedTitle);
|
||||
});
|
||||
|
||||
it('shows an appropriate message when subsequent "GET" returns HTTP500', function() {
|
||||
createInlineView(this);
|
||||
enableSaveButton();
|
||||
|
||||
// Save the updated settings
|
||||
inlineView.$('.action-save').click();
|
||||
|
||||
mockPatchRequest(['Inline_Discussion_1']);
|
||||
|
||||
// fake request for discussion/topics with GET method.
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'GET', '/mock_service/cohorts/discussion/topics'
|
||||
);
|
||||
AjaxHelpers.respondWithError(requests, 500);
|
||||
|
||||
var expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
|
||||
expect(inlineView.$('.message-title').text().trim()).toBe(expectedTitle);
|
||||
});
|
||||
|
||||
it('shows an appropriate error message for HTTP500', function() {
|
||||
createInlineView(this);
|
||||
enableSaveButton();
|
||||
|
||||
$(inlineView.$('.check-discussion-subcategory-inline')[1]).prop('checked', 'checked').change();
|
||||
inlineView.$('.action-save').click();
|
||||
|
||||
AjaxHelpers.respondWithError(requests, 500);
|
||||
var expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
|
||||
expect(inlineView.$('.message-title').text().trim()).toBe(expectedTitle);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
512
lms/static/js/spec/groups/views/discussions_spec.js
Normal file
512
lms/static/js/spec/groups/views/discussions_spec.js
Normal file
@@ -0,0 +1,512 @@
|
||||
define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
|
||||
'common/js/spec_helpers/template_helpers',
|
||||
'js/discussions_management/views/discussions', 'js/discussions_management/models/course_discussions_detail',
|
||||
'js/discussions_management/models/course_discussions_settings'
|
||||
],
|
||||
function(Backbone, $, AjaxHelpers, TemplateHelpers, DiscussionsView, CourseDiscussionTopicDetailsModel,
|
||||
CourseDiscussionsSettingsModel) {
|
||||
'use strict';
|
||||
|
||||
describe('Discussions View', function() {
|
||||
var createMockDiscussionsSettingsJson, createDiscussionsView, discussionsView, requests, verifyMessage,
|
||||
createMockDiscussionsSettings, createMockDiscussionsJson, createMockDiscussions,
|
||||
showAndAssertDiscussionTopics;
|
||||
|
||||
// Selectors
|
||||
var inlineDiscussionsFormCss = '.cohort-inline-discussions-form',
|
||||
courseWideDiscussionsFormCss = '.cohort-course-wide-discussions-form',
|
||||
courseWideDiscussionsSaveButtonCss = '.cohort-course-wide-discussions-form .action-save',
|
||||
inlineDiscussionsSaveButtonCss = '.cohort-inline-discussions-form .action-save';
|
||||
|
||||
createMockDiscussionsSettingsJson = function(dividedInlineDiscussions,
|
||||
dividedCourseWideDiscussions,
|
||||
alwaysDivideInlineDiscussions) {
|
||||
return {
|
||||
id: 0,
|
||||
divided_inline_discussions: dividedInlineDiscussions || [],
|
||||
divided_course_wide_discussions: dividedCourseWideDiscussions || [],
|
||||
always_divide_inline_discussions: alwaysDivideInlineDiscussions || false
|
||||
};
|
||||
};
|
||||
|
||||
createMockDiscussionsSettings = function(dividedInlineDiscussions,
|
||||
dividedCourseWideDiscussions,
|
||||
alwaysDivideInlineDiscussions) {
|
||||
return new CourseDiscussionsSettingsModel(
|
||||
createMockDiscussionsSettingsJson(dividedInlineDiscussions,
|
||||
dividedCourseWideDiscussions,
|
||||
alwaysDivideInlineDiscussions)
|
||||
);
|
||||
};
|
||||
|
||||
createMockDiscussionsJson = function(allDivided) {
|
||||
return {
|
||||
course_wide_discussions: {
|
||||
children: [['Topic_C_1', 'entry'], ['Topic_C_2', 'entry']],
|
||||
entries: {
|
||||
Topic_C_1: {
|
||||
sort_key: null,
|
||||
is_divided: true,
|
||||
id: 'Topic_C_1'
|
||||
},
|
||||
Topic_C_2: {
|
||||
sort_key: null,
|
||||
is_divided: false,
|
||||
id: 'Topic_C_2'
|
||||
}
|
||||
}
|
||||
},
|
||||
inline_discussions: {
|
||||
subcategories: {
|
||||
Topic_I_1: {
|
||||
subcategories: {},
|
||||
children: [['Inline_Discussion_1', 'entry'], ['Inline_Discussion_2', 'entry']],
|
||||
entries: {
|
||||
Inline_Discussion_1: {
|
||||
sort_key: null,
|
||||
is_divided: true,
|
||||
id: 'Inline_Discussion_1'
|
||||
},
|
||||
Inline_Discussion_2: {
|
||||
sort_key: null,
|
||||
is_divided: allDivided || false,
|
||||
id: 'Inline_Discussion_2'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
children: [['Topic_I_1', 'subcategory']]
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
createMockDiscussions = function(allDivided) {
|
||||
return new CourseDiscussionTopicDetailsModel(
|
||||
createMockDiscussionsJson(allDivided)
|
||||
);
|
||||
};
|
||||
|
||||
verifyMessage = function(expectedTitle, expectedMessageType, expectedAction, hasDetails) {
|
||||
expect(discussionsView.$('.message-title').text().trim()).toBe(expectedTitle);
|
||||
expect(discussionsView.$('div.message')).toHaveClass('message-' + expectedMessageType);
|
||||
if (expectedAction) {
|
||||
expect(discussionsView.$('.message-actions .action-primary').text().trim()).toBe(expectedAction);
|
||||
} else {
|
||||
expect(discussionsView.$('.message-actions .action-primary').length).toBe(0);
|
||||
} if (!hasDetails) {
|
||||
expect(discussionsView.$('.summary-items').length).toBe(0);
|
||||
}
|
||||
};
|
||||
|
||||
createDiscussionsView = function(test, options) {
|
||||
var discussionSettings, dividedDiscussions, discussionOptions;
|
||||
discussionOptions = options || {};
|
||||
discussionSettings = discussionOptions.cohortSettings || createMockDiscussionsSettings();
|
||||
discussionSettings.url = '/mock_service/discussions/settings';
|
||||
|
||||
dividedDiscussions = discussionOptions.dividedDiscussions || createMockDiscussions();
|
||||
dividedDiscussions.url = '/mock_service/discussion/topics';
|
||||
|
||||
requests = AjaxHelpers.requests(test);
|
||||
discussionsView = new DiscussionsView({
|
||||
el: $('.discussions-management'),
|
||||
discussionSettings: discussionSettings,
|
||||
context: {
|
||||
courseDiscussionTopicDetailsModel: dividedDiscussions
|
||||
}
|
||||
});
|
||||
discussionsView.render();
|
||||
};
|
||||
|
||||
showAndAssertDiscussionTopics = function() {
|
||||
var $courseWideDiscussionsForm,
|
||||
$inlineDiscussionsForm;
|
||||
|
||||
$courseWideDiscussionsForm = discussionsView.$(courseWideDiscussionsFormCss);
|
||||
$inlineDiscussionsForm = discussionsView.$(inlineDiscussionsFormCss);
|
||||
|
||||
// Discussions form should not be visible.
|
||||
expect($inlineDiscussionsForm.length).toBe(1);
|
||||
expect($courseWideDiscussionsForm.length).toBe(1);
|
||||
|
||||
expect($courseWideDiscussionsForm.text()).
|
||||
toContain('Course-Wide Discussion Topics');
|
||||
expect($courseWideDiscussionsForm.text()).
|
||||
toContain('Select the course-wide discussion topics that you want to divide by cohort.');
|
||||
|
||||
// Should see the inline discussions form and its content
|
||||
expect($inlineDiscussionsForm.length).toBe(1);
|
||||
expect($inlineDiscussionsForm.text()).
|
||||
toContain('Content-Specific Discussion Topics');
|
||||
expect($inlineDiscussionsForm.text()).
|
||||
toContain('Specify whether content-specific discussion topics are divided by cohort.');
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
setFixtures('<ul class="instructor-nav">' +
|
||||
'<li class="nav-item"><button type="button" data-section="discussion_management" ' +
|
||||
'class="active-section">Discussions</button></li></ul><div></div>' +
|
||||
'<div class="discussions-management"></div>');
|
||||
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/discussions');
|
||||
TemplateHelpers.installTemplate(
|
||||
'templates/instructor/instructor_dashboard_2/divided-discussions-course-wide'
|
||||
);
|
||||
TemplateHelpers.installTemplate(
|
||||
'templates/instructor/instructor_dashboard_2/divided-discussions-inline'
|
||||
);
|
||||
TemplateHelpers.installTemplate(
|
||||
'templates/instructor/instructor_dashboard_2/cohort-discussions-category'
|
||||
);
|
||||
TemplateHelpers.installTemplate(
|
||||
'templates/instructor/instructor_dashboard_2/cohort-discussions-subcategory'
|
||||
);
|
||||
TemplateHelpers.installTemplate('templates/instructor/instructor_dashboard_2/notification');
|
||||
});
|
||||
|
||||
describe('Discussion Topics', function() {
|
||||
var courseWideView, assertDividedTopics;
|
||||
|
||||
assertDividedTopics = function(view, type) {
|
||||
expect($('.check-discussion-subcategory-' + type).length).toBe(2);
|
||||
expect($('.check-discussion-subcategory-' + type + ':checked').length).toBe(1);
|
||||
};
|
||||
|
||||
it('renders the view properly', function() {
|
||||
createDiscussionsView(this);
|
||||
showAndAssertDiscussionTopics(this);
|
||||
});
|
||||
|
||||
describe('Course Wide', function() {
|
||||
it('shows the "Save" button as disabled initially', function() {
|
||||
createDiscussionsView(this);
|
||||
expect($(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('has one divided and one non-divided topic', function() {
|
||||
createDiscussionsView(this);
|
||||
assertDividedTopics(courseWideView, 'course-wide');
|
||||
|
||||
expect($('.course-wide-discussion-topics .divided-discussion-text').length).toBe(2);
|
||||
expect($('.course-wide-discussion-topics .divided-discussion-text.hidden').length).toBe(1);
|
||||
});
|
||||
|
||||
it('enables the "Save" button after changing checkbox', function() {
|
||||
createDiscussionsView(this);
|
||||
// save button is disabled.
|
||||
expect($(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
|
||||
|
||||
$($('.check-discussion-subcategory-course-wide')[0]).prop('checked', false).change();
|
||||
|
||||
// save button is enabled.
|
||||
expect($(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('saves the topic successfully', function() {
|
||||
createDiscussionsView(this);
|
||||
$($('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change();
|
||||
expect($(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
|
||||
|
||||
// Save the updated settings
|
||||
$('#cohort-course-wide-discussions-form .action-save').click();
|
||||
|
||||
// fake requests for discussions settings with PATCH method.
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'PATCH', '/mock_service/discussions/settings',
|
||||
{divided_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
|
||||
);
|
||||
AjaxHelpers.respondWithJson(
|
||||
requests,
|
||||
{divided_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
|
||||
);
|
||||
|
||||
// fake request for discussion/topics with GET method.
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'GET', '/mock_service/discussion/topics'
|
||||
);
|
||||
AjaxHelpers.respondWithJson(
|
||||
requests,
|
||||
createMockDiscussions()
|
||||
);
|
||||
|
||||
// verify the success message.
|
||||
expect($(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
|
||||
verifyMessage('Your changes have been saved.', 'confirmation');
|
||||
});
|
||||
|
||||
it('shows an appropriate message when subsequent "GET" returns HTTP500', function() {
|
||||
var expectedTitle;
|
||||
createDiscussionsView(this);
|
||||
$($('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change();
|
||||
expect($(courseWideDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
|
||||
|
||||
// Save the updated settings
|
||||
$('#cohort-course-wide-discussions-form .action-save').click();
|
||||
|
||||
// fake requests for discussion settings with PATCH method.
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'PATCH', '/mock_service/discussions/settings',
|
||||
{divided_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
|
||||
);
|
||||
AjaxHelpers.respondWithJson(
|
||||
requests,
|
||||
{divided_course_wide_discussions: ['Topic_C_1', 'Topic_C_2']}
|
||||
);
|
||||
|
||||
// fake request for discussion/topics with GET method.
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'GET', '/mock_service/discussion/topics'
|
||||
);
|
||||
AjaxHelpers.respondWithError(requests, 500);
|
||||
|
||||
expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
|
||||
expect($('.message-title').text().trim()).toBe(expectedTitle);
|
||||
});
|
||||
|
||||
it('shows an appropriate error message for HTTP500', function() {
|
||||
var expectedTitle;
|
||||
createDiscussionsView(this);
|
||||
$($('.check-discussion-subcategory-course-wide')[1]).prop('checked', 'checked').change();
|
||||
$('.action-save').click();
|
||||
|
||||
AjaxHelpers.respondWithError(requests, 500);
|
||||
expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
|
||||
expect($('.message-title').text().trim()).toBe(expectedTitle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Inline', function() {
|
||||
var enableSaveButton, mockGetRequest, verifySuccess, mockPatchRequest;
|
||||
|
||||
enableSaveButton = function() {
|
||||
// enable the inline discussion topics.
|
||||
$('.check-cohort-inline-discussions').prop('checked', 'checked').change();
|
||||
|
||||
$($('.check-discussion-subcategory-inline')[0]).prop('checked', 'checked').change();
|
||||
|
||||
expect($(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeFalsy();
|
||||
};
|
||||
|
||||
verifySuccess = function() {
|
||||
// verify the success message.
|
||||
expect($(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
|
||||
verifyMessage('Your changes have been saved.', 'confirmation');
|
||||
};
|
||||
|
||||
mockPatchRequest = function(dividededInlineDiscussions) {
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'PATCH', '/mock_service/discussions/settings',
|
||||
{
|
||||
divided_inline_discussions: dividededInlineDiscussions,
|
||||
always_divide_inline_discussions: false
|
||||
}
|
||||
);
|
||||
AjaxHelpers.respondWithJson(
|
||||
requests,
|
||||
{
|
||||
divided_inline_discussions: dividededInlineDiscussions,
|
||||
always_divide_inline_discussions: false
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
mockGetRequest = function(alldivided) {
|
||||
// fake request for discussion/topics with GET method.
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'GET', '/mock_service/discussion/topics'
|
||||
);
|
||||
AjaxHelpers.respondWithJson(
|
||||
requests,
|
||||
createMockDiscussions(alldivided)
|
||||
);
|
||||
};
|
||||
|
||||
it('shows the "Save" button as disabled initially', function() {
|
||||
createDiscussionsView(this);
|
||||
expect($(inlineDiscussionsSaveButtonCss).prop('disabled')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows always divide radio button as selected', function() {
|
||||
createDiscussionsView(this);
|
||||
$('.check-all-inline-discussions').prop('checked', 'checked').change();
|
||||
|
||||
// verify always divide inline discussions is being selected.
|
||||
expect($('.check-all-inline-discussions').prop('checked')).toBeTruthy();
|
||||
|
||||
// verify that inline topics are disabled
|
||||
expect($('.check-discussion-subcategory-inline').prop('disabled')).toBeTruthy();
|
||||
expect($('.check-discussion-category').prop('disabled')).toBeTruthy();
|
||||
|
||||
// verify that divide some topics are not being selected.
|
||||
expect($('.check-cohort-inline-discussions').prop('checked')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('shows divide some topics radio button as selected', function() {
|
||||
createDiscussionsView(this);
|
||||
$('.check-cohort-inline-discussions').prop('checked', 'checked').change();
|
||||
|
||||
// verify some divide inline discussions radio is being selected.
|
||||
expect($('.check-cohort-inline-discussions').prop('checked')).toBeTruthy();
|
||||
|
||||
// verify always divide radio is not selected.
|
||||
expect($('.check-all-inline-discussions').prop('checked')).toBeFalsy();
|
||||
|
||||
// verify that inline topics are enabled
|
||||
expect($('.check-discussion-subcategory-inline').prop('disabled')).toBeFalsy();
|
||||
expect($('.check-discussion-category').prop('disabled')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('has divided and non-divided topics', function() {
|
||||
createDiscussionsView(this);
|
||||
enableSaveButton();
|
||||
assertDividedTopics(this, 'inline');
|
||||
});
|
||||
|
||||
it('enables "Save" button after changing from always inline option', function() {
|
||||
createDiscussionsView(this);
|
||||
enableSaveButton();
|
||||
});
|
||||
|
||||
it('saves the topic', function() {
|
||||
createDiscussionsView(this);
|
||||
enableSaveButton();
|
||||
|
||||
// Save the updated settings
|
||||
$('.action-save').click();
|
||||
|
||||
mockPatchRequest(['Inline_Discussion_1']);
|
||||
mockGetRequest();
|
||||
|
||||
verifySuccess();
|
||||
});
|
||||
|
||||
it('selects the parent category when all children are selected', function() {
|
||||
createDiscussionsView(this);
|
||||
enableSaveButton();
|
||||
|
||||
// parent category should be indeterminate.
|
||||
expect($('.check-discussion-category:checked').length).toBe(0);
|
||||
expect($('.check-discussion-category:indeterminate').length).toBe(1);
|
||||
|
||||
$('.check-discussion-subcategory-inline').prop('checked', 'checked').change();
|
||||
// parent should be checked as we checked all children
|
||||
expect($('.check-discussion-category:checked').length).toBe(1);
|
||||
});
|
||||
|
||||
it('selects/deselects all children when a parent category is selected/deselected', function() {
|
||||
createDiscussionsView(this);
|
||||
enableSaveButton();
|
||||
|
||||
expect($('.check-discussion-category:checked').length).toBe(0);
|
||||
|
||||
$('.check-discussion-category').prop('checked', 'checked').change();
|
||||
|
||||
expect($('.check-discussion-category:checked').length).toBe(1);
|
||||
expect($('.check-discussion-subcategory-inline:checked').length).toBe(2);
|
||||
|
||||
// un-check the parent, all children should be unchecd.
|
||||
$('.check-discussion-category').prop('checked', false).change();
|
||||
expect($('.check-discussion-category:checked').length).toBe(0);
|
||||
expect($('.check-discussion-subcategory-inline:checked').length).toBe(0);
|
||||
});
|
||||
|
||||
it('saves correctly when a subset of topics are selected within a category', function() {
|
||||
createDiscussionsView(this);
|
||||
enableSaveButton();
|
||||
|
||||
// parent category should be indeterminate.
|
||||
expect($('.check-discussion-category:checked').length).toBe(0);
|
||||
expect($('.check-discussion-category:indeterminate').length).toBe(1);
|
||||
|
||||
// Save the updated settings
|
||||
$('#cohort-inline-discussions-form .action-save').click();
|
||||
|
||||
mockPatchRequest(['Inline_Discussion_1']);
|
||||
mockGetRequest();
|
||||
|
||||
verifySuccess();
|
||||
// parent category should be indeterminate.
|
||||
expect($('.check-discussion-category:indeterminate').length).toBe(1);
|
||||
});
|
||||
|
||||
it('saves correctly when all child topics are selected within a category', function() {
|
||||
createDiscussionsView(this);
|
||||
enableSaveButton();
|
||||
|
||||
// parent category should be indeterminate.
|
||||
expect($('.check-discussion-category:checked').length).toBe(0);
|
||||
expect($('.check-discussion-category:indeterminate').length).toBe(1);
|
||||
|
||||
$('.check-discussion-subcategory-inline').prop('checked', 'checked').change();
|
||||
// Save the updated settings
|
||||
$('#cohort-inline-discussions-form .action-save').click();
|
||||
|
||||
mockPatchRequest(['Inline_Discussion_1', 'Inline_Discussion_2']);
|
||||
mockGetRequest(true);
|
||||
|
||||
verifySuccess();
|
||||
// parent category should be checked.
|
||||
expect($('.check-discussion-category:checked').length).toBe(1);
|
||||
});
|
||||
|
||||
it('shows an appropriate message when no inline topics exist', function() {
|
||||
var topicsJson, options, expectedTitle;
|
||||
|
||||
topicsJson = {
|
||||
course_wide_discussions: {
|
||||
children: [['Topic_C_1', 'entry']],
|
||||
entries: {
|
||||
Topic_C_1: {
|
||||
sort_key: null,
|
||||
is_divided: true,
|
||||
id: 'Topic_C_1'
|
||||
}
|
||||
}
|
||||
},
|
||||
inline_discussions: {
|
||||
subcategories: {},
|
||||
children: []
|
||||
}
|
||||
};
|
||||
options = {dividedDiscussions: new CourseDiscussionTopicDetailsModel(topicsJson)};
|
||||
createDiscussionsView(this, options);
|
||||
|
||||
expectedTitle = 'No content-specific discussion topics exist.';
|
||||
expect($('.no-topics').text().trim()).toBe(expectedTitle);
|
||||
});
|
||||
|
||||
it('shows an appropriate message when subsequent "GET" returns HTTP500', function() {
|
||||
var expectedTitle;
|
||||
createDiscussionsView(this);
|
||||
enableSaveButton();
|
||||
|
||||
// Save the updated settings
|
||||
$('#cohort-inline-discussions-form .action-save').click();
|
||||
|
||||
mockPatchRequest(['Inline_Discussion_1']);
|
||||
|
||||
// fake request for discussion/topics with GET method.
|
||||
AjaxHelpers.expectJsonRequest(
|
||||
requests, 'GET', '/mock_service/discussion/topics'
|
||||
);
|
||||
AjaxHelpers.respondWithError(requests, 500);
|
||||
|
||||
expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
|
||||
expect($('.message-title').text().trim()).toBe(expectedTitle);
|
||||
});
|
||||
|
||||
it('shows an appropriate error message for HTTP500', function() {
|
||||
var expectedTitle;
|
||||
createDiscussionsView(this);
|
||||
enableSaveButton();
|
||||
|
||||
$($('.check-discussion-subcategory-inline')[1]).prop('checked', 'checked').change();
|
||||
$('#cohort-inline-discussions-form .action-save').click();
|
||||
|
||||
AjaxHelpers.respondWithError(requests, 500);
|
||||
expectedTitle = "We've encountered an error. Refresh your browser and then try again.";
|
||||
expect($('.message-title').text().trim()).toBe(expectedTitle);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -28,6 +28,7 @@
|
||||
'js/edxnotes/views/page_factory',
|
||||
'js/financial-assistance/financial_assistance_form_factory',
|
||||
'js/groups/views/cohorts_dashboard_factory',
|
||||
'js/discussions_management/views/discussions_dashboard_factory',
|
||||
'js/header_factory',
|
||||
'js/learner_dashboard/program_details_factory',
|
||||
'js/learner_dashboard/program_list_factory',
|
||||
|
||||
@@ -731,6 +731,7 @@
|
||||
'js/spec/edxnotes/views/visibility_decorator_spec.js',
|
||||
'js/spec/financial-assistance/financial_assistance_form_view_spec.js',
|
||||
'js/spec/groups/views/cohorts_spec.js',
|
||||
'js/spec/groups/views/discussions_spec.js',
|
||||
'js/spec/instructor_dashboard/certificates_bulk_exception_spec.js',
|
||||
'js/spec/instructor_dashboard/certificates_exception_spec.js',
|
||||
'js/spec/instructor_dashboard/certificates_invalidation_spec.js',
|
||||
|
||||
@@ -1181,49 +1181,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// cohort discussions interface.
|
||||
.cohort-discussions-nav {
|
||||
|
||||
.cohort-course-wide-discussions-form {
|
||||
|
||||
.form-actions {
|
||||
padding-top: ($baseline/2);
|
||||
}
|
||||
}
|
||||
|
||||
.category-title,
|
||||
.topic-name,
|
||||
.all-inline-discussions,
|
||||
.always_cohort_inline_discussions,
|
||||
.cohort_inline_discussions {
|
||||
padding-left: ($baseline/2);
|
||||
}
|
||||
|
||||
.always_cohort_inline_discussions,
|
||||
.cohort_inline_discussions {
|
||||
padding-top: ($baseline/2);
|
||||
}
|
||||
|
||||
.category-item,
|
||||
.subcategory-item {
|
||||
padding-top: ($baseline/2);
|
||||
}
|
||||
|
||||
.cohorted-text {
|
||||
color: $uxpl-blue-base;
|
||||
}
|
||||
|
||||
.discussions-wrapper {
|
||||
@extend %ui-no-list;
|
||||
padding: 0 ($baseline/2);
|
||||
|
||||
.subcategories {
|
||||
padding: 0 ($baseline*1.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.wrapper-tabs { // This applies to the tab-like interface that toggles between the student management and the group settings
|
||||
@extend %ui-no-list;
|
||||
@extend %ui-depth1;
|
||||
@@ -1280,6 +1237,77 @@
|
||||
}
|
||||
}
|
||||
|
||||
// view - discussions management
|
||||
// --------------------
|
||||
.instructor-dashboard-wrapper-2 section.idash-section#discussions_management {
|
||||
|
||||
.form-submit {
|
||||
@include idashbutton($uxpl-blue-base);
|
||||
@include font-size(14);
|
||||
@include line-height(14);
|
||||
margin-right: ($baseline/2);
|
||||
margin-bottom: 0;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.discussions-management-supplemental {
|
||||
@extend %t-copy-sub1;
|
||||
margin-top: $baseline;
|
||||
padding: ($baseline/2) $baseline;
|
||||
background: $gray-l6;
|
||||
border-radius: ($baseline/10);
|
||||
}
|
||||
|
||||
// cohort discussions interface.
|
||||
.discussions-nav {
|
||||
|
||||
.cohort-course-wide-discussions-form {
|
||||
|
||||
.form-actions {
|
||||
padding-top: ($baseline/2);
|
||||
}
|
||||
}
|
||||
|
||||
.category-title,
|
||||
.topic-name,
|
||||
.all-inline-discussions,
|
||||
.always_divide_inline_discussions,
|
||||
.divide_inline_discussions {
|
||||
padding-left: ($baseline/2);
|
||||
}
|
||||
|
||||
.always_divide_inline_discussions,
|
||||
.divide_inline_discussions {
|
||||
padding-top: ($baseline/2);
|
||||
}
|
||||
|
||||
.category-item,
|
||||
.subcategory-item {
|
||||
padding-top: ($baseline/2);
|
||||
}
|
||||
|
||||
.divided-discussion-text{
|
||||
color: $uxpl-blue-base;
|
||||
}
|
||||
|
||||
.discussions-wrapper {
|
||||
@extend %ui-no-list;
|
||||
padding: 0 ($baseline/2);
|
||||
|
||||
.subcategories {
|
||||
padding: 0 ($baseline*1.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
.wrapper-tabs {
|
||||
@extend %ui-no-list;
|
||||
@extend %ui-depth1;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
padding: 0 $baseline;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// view - student admin
|
||||
// --------------------
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<label>
|
||||
<input data-id="<%- id %>" class="check-discussion-subcategory-<%- type %>" type="checkbox" <%- is_divided ? 'checked="checked"' : '' %> />
|
||||
<span class="topic-name"><%- name %></span>
|
||||
<span class="cohorted-text <%- is_divided ? '' : 'hidden'%>">- <%- gettext('Cohorted') %></span>
|
||||
<span class="divided-discussion-text <%- is_divided ? '' : 'hidden'%>">- <%- gettext('Divided') %></span>
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -13,7 +13,6 @@ from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_
|
||||
data-cohorts_url="${section_data['cohorts_url']}"
|
||||
data-upload_cohorts_csv_url="${section_data['upload_cohorts_csv_url']}"
|
||||
data-course_cohort_settings_url="${section_data['course_cohort_settings_url']}"
|
||||
data-discussion-topics-url="${section_data['discussion_topics_url']}"
|
||||
data-verified_track_cohorting_url="${section_data['verified_track_cohorting_url']}"
|
||||
data-is_ccx_enabled="${'true' if section_data['ccx_is_enabled'] else 'false'}"
|
||||
>
|
||||
|
||||
@@ -49,15 +49,5 @@
|
||||
%>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr class="divider divider-lv1" />
|
||||
|
||||
<!-- Discussion Topics. -->
|
||||
<button class="toggle-cohort-management-discussions" data-href="#cohort-discussions-management"><%- gettext('Specify whether discussion topics are divided by cohort') %></button>
|
||||
<div class="cohort-discussions-nav is-hidden" id="cohort-discussions-management" tabindex="-1">
|
||||
<div class="cohort-course-wide-discussions-nav"></div>
|
||||
<div class="cohort-inline-discussions-nav"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<!-- Discussion Topics. -->
|
||||
<div class="discussions-nav" id="discussions-management" tabindex="-1">
|
||||
<div class="course-wide-discussions-nav"></div>
|
||||
<div class="inline-discussions-nav"></div>
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
<%page expression_filter="h" args="section_data"/>
|
||||
|
||||
<%namespace name='static' file='../../static_content.html'/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.js_utils import js_escaped_string, dump_js_escaped_json
|
||||
from courseware.courses import get_studio_url
|
||||
from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_user_partition
|
||||
%>
|
||||
|
||||
|
||||
<div class="discussions-management"
|
||||
data-discussion-topics-url="${section_data['discussion_topics_url']}"
|
||||
data-course-discussion-settings-url="${section_data['course_discussion_settings']}"
|
||||
>
|
||||
</div>
|
||||
|
||||
<%block name="js_extra">
|
||||
<%static:require_module module_name="js/discussions_management/views/discussions_dashboard_factory" class_name="DiscussionsFactory">
|
||||
DiscussionsFactory();
|
||||
</%static:require_module>
|
||||
</%block>
|
||||
@@ -1,6 +1,6 @@
|
||||
<h3 class="hd hd-3 subsection-title"><%- gettext('Specify whether discussion topics are divided by cohort') %></h3>
|
||||
<form action="" method="post" id="cohort-course-wide-discussions-form" class="cohort-course-wide-discussions-form">
|
||||
<div class="wrapper cohort-management-supplemental">
|
||||
<div class="wrapper discussions-management-supplemental">
|
||||
<div class="form-fields">
|
||||
<div class="form-field">
|
||||
<div class="course-wide-discussion-topics">
|
||||
@@ -1,21 +1,21 @@
|
||||
<hr class="divider divider-lv1" />
|
||||
|
||||
<form action="" method="post" id="cohort-inline-discussions-form" class="cohort-inline-discussions-form">
|
||||
<div class="wrapper cohort-management-supplemental">
|
||||
<div class="wrapper discussions-management-supplemental">
|
||||
<div class="form-fields">
|
||||
<div class="form-field">
|
||||
<div class="inline-discussion-topics">
|
||||
<h4 class="hd hd-4 subsection-title"><%- gettext('Content-Specific Discussion Topics') %></h4>
|
||||
<p><%- gettext('Specify whether content-specific discussion topics are divided by cohort.') %></p>
|
||||
<div class="always_cohort_inline_discussions">
|
||||
<div class="always_divide_inline_discussions">
|
||||
<label>
|
||||
<input type="radio" name="inline" class="check-all-inline-discussions" <%- alwaysCohortInlineDiscussions ? 'checked="checked"' : '' %>/>
|
||||
<input type="radio" name="inline" class="check-all-inline-discussions" <%- alwaysDivideInlineDiscussions ? 'checked="checked"' : '' %>/>
|
||||
<span class="all-inline-discussions"><%- gettext('Always cohort content-specific discussion topics') %></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="cohort_inline_discussions">
|
||||
<div class="divide_inline_discussions">
|
||||
<label>
|
||||
<input type="radio" name="inline" class="check-cohort-inline-discussions" <%- alwaysCohortInlineDiscussions ? '' : 'checked="checked"' %>/>
|
||||
<input type="radio" name="inline" class="check-cohort-inline-discussions" <%- alwaysDivideInlineDiscussions ? '' : 'checked="checked"' %>/>
|
||||
<span class="all-inline-discussions"><%- gettext('Cohort selected content-specific discussion topics') %></span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -81,7 +81,7 @@ from openedx.core.djangolib.markup import HTML
|
||||
|
||||
## Include Underscore templates
|
||||
<%block name="header_extras">
|
||||
% for template_name in ["cohorts", "enrollment-code-lookup-links", "cohort-editor", "cohort-group-header", "cohort-selector", "cohort-form", "notification", "cohort-state", "cohort-discussions-inline", "cohort-discussions-course-wide", "cohort-discussions-category", "cohort-discussions-subcategory", "certificate-white-list", "certificate-white-list-editor", "certificate-bulk-white-list", "certificate-invalidation"]:
|
||||
% for template_name in ["cohorts", "discussions", "enrollment-code-lookup-links", "cohort-editor", "cohort-group-header", "cohort-selector", "cohort-form", "notification", "cohort-state", "divided-discussions-inline", "divided-discussions-course-wide", "cohort-discussions-category", "cohort-discussions-subcategory", "certificate-white-list", "certificate-white-list-editor", "certificate-bulk-white-list", "certificate-invalidation"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="instructor/instructor_dashboard_2/${template_name}.underscore" />
|
||||
</script>
|
||||
|
||||
15
lms/urls.py
15
lms/urls.py
@@ -504,6 +504,15 @@ urlpatterns += (
|
||||
include(COURSE_URLS)
|
||||
),
|
||||
|
||||
# Discussions Management
|
||||
url(
|
||||
r'^courses/{}/discussions/settings$'.format(
|
||||
settings.COURSE_KEY_PATTERN,
|
||||
),
|
||||
'openedx.core.djangoapps.course_groups.views.course_discussions_settings_handler',
|
||||
name='course_discussions_settings',
|
||||
),
|
||||
|
||||
# Cohorts management
|
||||
url(
|
||||
r'^courses/{}/cohorts/settings$'.format(
|
||||
@@ -548,11 +557,11 @@ urlpatterns += (
|
||||
name='debug_cohort_mgmt',
|
||||
),
|
||||
url(
|
||||
r'^courses/{}/cohorts/topics$'.format(
|
||||
r'^courses/{}/discussion/topics$'.format(
|
||||
settings.COURSE_KEY_PATTERN,
|
||||
),
|
||||
'openedx.core.djangoapps.course_groups.views.cohort_discussion_topics',
|
||||
name='cohort_discussion_topics',
|
||||
'openedx.core.djangoapps.course_groups.views.discussion_topics',
|
||||
name='discussion_topics',
|
||||
),
|
||||
url(
|
||||
r'^courses/{}/verified_track_content/settings'.format(
|
||||
|
||||
@@ -121,6 +121,16 @@ def is_course_cohorted(course_key):
|
||||
return _get_course_cohort_settings(course_key).is_cohorted
|
||||
|
||||
|
||||
def get_course_cohort_id(course_key):
|
||||
"""
|
||||
Given a course key, return the int id for the cohort settings.
|
||||
|
||||
Raises:
|
||||
Http404 if the course doesn't exist.
|
||||
"""
|
||||
return _get_course_cohort_settings(course_key).id
|
||||
|
||||
|
||||
def set_course_cohorted(course_key, cohorted):
|
||||
"""
|
||||
Given a course course and a boolean, sets whether or not the course is cohorted.
|
||||
|
||||
@@ -135,43 +135,72 @@ def config_course_cohorts_legacy(
|
||||
pass
|
||||
|
||||
|
||||
# pylint: disable=dangerous-default-value
|
||||
def config_course_discussions(
|
||||
course,
|
||||
discussion_topics={},
|
||||
divided_discussions=[],
|
||||
always_divide_inline_discussions=False
|
||||
):
|
||||
"""
|
||||
Set discussions and configure divided discussions for a course.
|
||||
|
||||
Arguments:
|
||||
course: CourseDescriptor
|
||||
discussion_topics (Dict): Discussion topic names. Picks ids and
|
||||
sort_keys automatically.
|
||||
divided_discussions: Discussion topics to divide. Converts the
|
||||
list to use the same ids as discussion topic names.
|
||||
always_divide_inline_discussions (bool): Whether inline discussions
|
||||
should be divided by default.
|
||||
|
||||
Returns:
|
||||
Nothing -- modifies course in place.
|
||||
"""
|
||||
|
||||
def to_id(name):
|
||||
"""Convert name to id."""
|
||||
return topic_name_to_id(course, name)
|
||||
|
||||
set_course_discussion_settings(
|
||||
course.id,
|
||||
divided_discussions=[to_id(name) for name in divided_discussions],
|
||||
always_divide_inline_discussions=always_divide_inline_discussions,
|
||||
division_scheme=CourseDiscussionSettings.COHORT,
|
||||
)
|
||||
|
||||
course.discussion_topics = dict((name, {"sort_key": "A", "id": to_id(name)})
|
||||
for name in discussion_topics)
|
||||
try:
|
||||
# Not implemented for XMLModulestore, which is used by test_cohorts.
|
||||
modulestore().update_item(course, ModuleStoreEnum.UserID.test)
|
||||
except NotImplementedError:
|
||||
pass
|
||||
|
||||
|
||||
# pylint: disable=dangerous-default-value
|
||||
def config_course_cohorts(
|
||||
course,
|
||||
is_cohorted,
|
||||
auto_cohorts=[],
|
||||
manual_cohorts=[],
|
||||
discussion_topics=[],
|
||||
divided_discussions=[],
|
||||
always_divide_inline_discussions=False
|
||||
):
|
||||
"""
|
||||
Set discussions and configure cohorts for a course.
|
||||
Set and configure cohorts for a course.
|
||||
|
||||
Arguments:
|
||||
course: CourseDescriptor
|
||||
is_cohorted (bool): Is the course cohorted?
|
||||
auto_cohorts (list): Names of auto cohorts to create.
|
||||
manual_cohorts (list): Names of manual cohorts to create.
|
||||
discussion_topics (list): Discussion topic names. Picks ids and
|
||||
sort_keys automatically.
|
||||
divided_discussions: Discussion topics to divide. Converts the
|
||||
list to use the same ids as discussion topic names.
|
||||
always_divide_inline_discussions (bool): Whether inline discussions
|
||||
should be divided by default.
|
||||
|
||||
Returns:
|
||||
Nothing -- modifies course in place.
|
||||
"""
|
||||
def to_id(name):
|
||||
"""Convert name to id."""
|
||||
return topic_name_to_id(course, name)
|
||||
|
||||
set_course_cohorted(course.id, is_cohorted)
|
||||
set_course_discussion_settings(
|
||||
course.id,
|
||||
divided_discussions=[to_id(name) for name in divided_discussions],
|
||||
always_divide_inline_discussions=always_divide_inline_discussions,
|
||||
division_scheme=CourseDiscussionSettings.COHORT,
|
||||
)
|
||||
|
||||
@@ -183,8 +212,6 @@ def config_course_cohorts(
|
||||
cohort = CohortFactory(course_id=course.id, name=cohort_name)
|
||||
CourseCohortFactory(course_user_group=cohort, assignment_type=CourseCohort.MANUAL)
|
||||
|
||||
course.discussion_topics = dict((name, {"sort_key": "A", "id": to_id(name)})
|
||||
for name in discussion_topics)
|
||||
try:
|
||||
# Not implemented for XMLModulestore, which is used by test_cohorts.
|
||||
modulestore().update_item(course, ModuleStoreEnum.UserID.test)
|
||||
|
||||
@@ -25,15 +25,17 @@ from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
|
||||
from ..models import CourseUserGroup, CourseCohort
|
||||
from ..views import (
|
||||
course_cohort_settings_handler, cohort_handler, users_in_cohort, add_users_to_cohort, remove_user_from_cohort,
|
||||
link_cohort_to_partition_group, cohort_discussion_topics
|
||||
course_cohort_settings_handler, course_discussions_settings_handler,
|
||||
cohort_handler, users_in_cohort,
|
||||
add_users_to_cohort, remove_user_from_cohort,
|
||||
link_cohort_to_partition_group, discussion_topics
|
||||
)
|
||||
from ..cohorts import (
|
||||
get_cohort, get_cohort_by_name, get_cohort_by_id,
|
||||
DEFAULT_COHORT_NAME, get_group_info_for_cohort
|
||||
)
|
||||
from .helpers import (
|
||||
config_course_cohorts, config_course_cohorts_legacy, CohortFactory, CourseCohortFactory, topic_name_to_id
|
||||
config_course_cohorts, config_course_discussions, config_course_cohorts_legacy, CohortFactory, CourseCohortFactory, topic_name_to_id
|
||||
)
|
||||
|
||||
|
||||
@@ -99,13 +101,13 @@ class CohortViewsTestCase(ModuleStoreTestCase):
|
||||
view_args.insert(0, request)
|
||||
self.assertRaises(Http404, view, *view_args)
|
||||
|
||||
def create_cohorted_discussions(self):
|
||||
def create_divided_discussions(self):
|
||||
"""
|
||||
Set up a cohorted discussion in the system, complete with all the fixings
|
||||
Set up a divided discussion in the system, complete with all the fixings
|
||||
"""
|
||||
cohorted_inline_discussions = ['Topic A']
|
||||
cohorted_course_wide_discussions = ["Topic B"]
|
||||
cohorted_discussions = cohorted_inline_discussions + cohorted_course_wide_discussions
|
||||
divided_inline_discussions = ['Topic A']
|
||||
divided_course_wide_discussions = ["Topic B"]
|
||||
divided_discussions = divided_inline_discussions + divided_course_wide_discussions
|
||||
|
||||
# inline discussion
|
||||
ItemFactory.create(
|
||||
@@ -124,10 +126,14 @@ class CohortViewsTestCase(ModuleStoreTestCase):
|
||||
config_course_cohorts(
|
||||
self.course,
|
||||
is_cohorted=True,
|
||||
discussion_topics=discussion_topics,
|
||||
divided_discussions=cohorted_discussions
|
||||
)
|
||||
return cohorted_inline_discussions, cohorted_course_wide_discussions
|
||||
|
||||
config_course_discussions(
|
||||
self.course,
|
||||
discussion_topics=discussion_topics,
|
||||
divided_discussions=divided_discussions
|
||||
)
|
||||
return divided_inline_discussions, divided_course_wide_discussions
|
||||
|
||||
def get_handler(self, course, cohort=None, expected_response_code=200, handler=cohort_handler):
|
||||
"""
|
||||
@@ -177,6 +183,129 @@ class CohortViewsTestCase(ModuleStoreTestCase):
|
||||
return json.loads(response.content)
|
||||
|
||||
|
||||
@attr(shard=2)
|
||||
class CourseDiscussionsHandlerTestCase(CohortViewsTestCase):
|
||||
"""
|
||||
Tests the course_discussion_settings_handler
|
||||
"""
|
||||
def get_expected_response(self):
|
||||
"""
|
||||
Returns the static response dict.
|
||||
"""
|
||||
return {
|
||||
u'always_divide_inline_discussions': False,
|
||||
u'divided_inline_discussions': [],
|
||||
u'divided_course_wide_discussions': [],
|
||||
u'id': 1
|
||||
}
|
||||
|
||||
def test_non_staff(self):
|
||||
"""
|
||||
Verify that we cannot access course_discussions_settings_handler if we're a non-staff user.
|
||||
"""
|
||||
self._verify_non_staff_cannot_access(course_discussions_settings_handler, "GET", [unicode(self.course.id)])
|
||||
self._verify_non_staff_cannot_access(course_discussions_settings_handler, "PATCH", [unicode(self.course.id)])
|
||||
|
||||
def test_update_always_divide_inline_discussion_settings(self):
|
||||
"""
|
||||
Verify that course_discussions_settings_handler is working for always_divide_inline_discussions via HTTP PATCH.
|
||||
"""
|
||||
config_course_cohorts(self.course, is_cohorted=True)
|
||||
|
||||
response = self.get_handler(self.course, handler=course_discussions_settings_handler)
|
||||
|
||||
expected_response = self.get_expected_response()
|
||||
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
expected_response['always_divide_inline_discussions'] = True
|
||||
response = self.patch_handler(self.course, data=expected_response, handler=course_discussions_settings_handler)
|
||||
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
def test_update_course_wide_discussion_settings(self):
|
||||
"""
|
||||
Verify that course_discussions_settings_handler is working for divided_course_wide_discussions via HTTP PATCH.
|
||||
"""
|
||||
# course-wide discussion
|
||||
discussion_topics = {
|
||||
"Topic B": {"id": "Topic B"},
|
||||
}
|
||||
|
||||
config_course_cohorts(self.course, is_cohorted=True)
|
||||
config_course_discussions(self.course, discussion_topics=discussion_topics)
|
||||
|
||||
response = self.get_handler(self.course, handler=course_discussions_settings_handler)
|
||||
|
||||
expected_response = self.get_expected_response()
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
expected_response['divided_course_wide_discussions'] = [topic_name_to_id(self.course, "Topic B")]
|
||||
response = self.patch_handler(self.course, data=expected_response, handler=course_discussions_settings_handler)
|
||||
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
def test_update_inline_discussion_settings(self):
|
||||
"""
|
||||
Verify that course_discussions_settings_handler is working for divided_inline_discussions via HTTP PATCH.
|
||||
"""
|
||||
config_course_cohorts(self.course, is_cohorted=True)
|
||||
|
||||
response = self.get_handler(self.course, handler=course_discussions_settings_handler)
|
||||
|
||||
expected_response = self.get_expected_response()
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
now = datetime.now()
|
||||
# inline discussion
|
||||
ItemFactory.create(
|
||||
parent_location=self.course.location,
|
||||
category="discussion",
|
||||
discussion_id="Topic_A",
|
||||
discussion_category="Chapter",
|
||||
discussion_target="Discussion",
|
||||
start=now
|
||||
)
|
||||
|
||||
expected_response['divided_inline_discussions'] = ["Topic_A"]
|
||||
response = self.patch_handler(self.course, data=expected_response, handler=course_discussions_settings_handler)
|
||||
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
def test_get_settings(self):
|
||||
"""
|
||||
Verify that course_discussions_settings_handler is working for HTTP GET.
|
||||
"""
|
||||
divided_inline_discussions, divided_course_wide_discussions = self.create_divided_discussions()
|
||||
|
||||
response = self.get_handler(self.course, handler=course_discussions_settings_handler)
|
||||
expected_response = self.get_expected_response()
|
||||
|
||||
expected_response['divided_inline_discussions'] = [topic_name_to_id(self.course, name)
|
||||
for name in divided_inline_discussions]
|
||||
expected_response['divided_course_wide_discussions'] = [topic_name_to_id(self.course, name)
|
||||
for name in divided_course_wide_discussions]
|
||||
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
def test_update_settings_with_invalid_field_data_type(self):
|
||||
"""
|
||||
Verify that course_discussions_settings_handler return HTTP 400 if field data type is incorrect.
|
||||
"""
|
||||
config_course_cohorts(self.course, is_cohorted=True)
|
||||
|
||||
response = self.patch_handler(
|
||||
self.course,
|
||||
data={'always_divide_inline_discussions': ''},
|
||||
expected_response_code=400,
|
||||
handler=course_discussions_settings_handler
|
||||
)
|
||||
self.assertEqual(
|
||||
"Incorrect field type for `{}`. Type must be `{}`".format('always_divide_inline_discussions', bool.__name__),
|
||||
response.get("error")
|
||||
)
|
||||
|
||||
|
||||
@attr(shard=2)
|
||||
class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
|
||||
"""
|
||||
@@ -189,9 +318,6 @@ class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
|
||||
"""
|
||||
return {
|
||||
'is_cohorted': True,
|
||||
'always_cohort_inline_discussions': False,
|
||||
'cohorted_inline_discussions': [],
|
||||
'cohorted_course_wide_discussions': [],
|
||||
'id': 1
|
||||
}
|
||||
|
||||
@@ -202,22 +328,6 @@ class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
|
||||
self._verify_non_staff_cannot_access(course_cohort_settings_handler, "GET", [unicode(self.course.id)])
|
||||
self._verify_non_staff_cannot_access(course_cohort_settings_handler, "PATCH", [unicode(self.course.id)])
|
||||
|
||||
def test_get_settings(self):
|
||||
"""
|
||||
Verify that course_cohort_settings_handler is working for HTTP GET.
|
||||
"""
|
||||
cohorted_inline_discussions, cohorted_course_wide_discussions = self.create_cohorted_discussions()
|
||||
|
||||
response = self.get_handler(self.course, handler=course_cohort_settings_handler)
|
||||
expected_response = self.get_expected_response()
|
||||
|
||||
expected_response['cohorted_inline_discussions'] = [topic_name_to_id(self.course, name)
|
||||
for name in cohorted_inline_discussions]
|
||||
expected_response['cohorted_course_wide_discussions'] = [topic_name_to_id(self.course, name)
|
||||
for name in cohorted_course_wide_discussions]
|
||||
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
def test_update_is_cohorted_settings(self):
|
||||
"""
|
||||
Verify that course_cohort_settings_handler is working for is_cohorted via HTTP PATCH.
|
||||
@@ -235,71 +345,6 @@ class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
|
||||
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
def test_update_always_cohort_inline_discussion_settings(self):
|
||||
"""
|
||||
Verify that course_cohort_settings_handler is working for always_cohort_inline_discussions via HTTP PATCH.
|
||||
"""
|
||||
config_course_cohorts(self.course, is_cohorted=True)
|
||||
|
||||
response = self.get_handler(self.course, handler=course_cohort_settings_handler)
|
||||
|
||||
expected_response = self.get_expected_response()
|
||||
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
expected_response['always_cohort_inline_discussions'] = True
|
||||
response = self.patch_handler(self.course, data=expected_response, handler=course_cohort_settings_handler)
|
||||
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
def test_update_course_wide_discussion_settings(self):
|
||||
"""
|
||||
Verify that course_cohort_settings_handler is working for cohorted_course_wide_discussions via HTTP PATCH.
|
||||
"""
|
||||
# course-wide discussion
|
||||
discussion_topics = {
|
||||
"Topic B": {"id": "Topic B"},
|
||||
}
|
||||
|
||||
config_course_cohorts(self.course, is_cohorted=True, discussion_topics=discussion_topics)
|
||||
|
||||
response = self.get_handler(self.course, handler=course_cohort_settings_handler)
|
||||
|
||||
expected_response = self.get_expected_response()
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
expected_response['cohorted_course_wide_discussions'] = [topic_name_to_id(self.course, "Topic B")]
|
||||
response = self.patch_handler(self.course, data=expected_response, handler=course_cohort_settings_handler)
|
||||
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
def test_update_inline_discussion_settings(self):
|
||||
"""
|
||||
Verify that course_cohort_settings_handler is working for cohorted_inline_discussions via HTTP PATCH.
|
||||
"""
|
||||
config_course_cohorts(self.course, is_cohorted=True)
|
||||
|
||||
response = self.get_handler(self.course, handler=course_cohort_settings_handler)
|
||||
|
||||
expected_response = self.get_expected_response()
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
now = datetime.now()
|
||||
# inline discussion
|
||||
ItemFactory.create(
|
||||
parent_location=self.course.location,
|
||||
category="discussion",
|
||||
discussion_id="Topic_A",
|
||||
discussion_category="Chapter",
|
||||
discussion_target="Discussion",
|
||||
start=now
|
||||
)
|
||||
|
||||
expected_response['cohorted_inline_discussions'] = ["Topic_A"]
|
||||
response = self.patch_handler(self.course, data=expected_response, handler=course_cohort_settings_handler)
|
||||
|
||||
self.assertEqual(response, expected_response)
|
||||
|
||||
def test_update_settings_with_missing_field(self):
|
||||
"""
|
||||
Verify that course_cohort_settings_handler return HTTP 400 if required data field is missing from post data.
|
||||
@@ -315,17 +360,6 @@ class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
|
||||
"""
|
||||
config_course_cohorts(self.course, is_cohorted=True)
|
||||
|
||||
response = self.patch_handler(
|
||||
self.course,
|
||||
data={'always_cohort_inline_discussions': ''},
|
||||
expected_response_code=400,
|
||||
handler=course_cohort_settings_handler
|
||||
)
|
||||
self.assertEqual(
|
||||
"Incorrect field type for `{}`. Type must be `{}`".format('always_divide_inline_discussions', bool.__name__),
|
||||
response.get("error")
|
||||
)
|
||||
|
||||
response = self.patch_handler(
|
||||
self.course,
|
||||
data={'is_cohorted': ''},
|
||||
@@ -1240,25 +1274,25 @@ class RemoveUserFromCohortTestCase(CohortViewsTestCase):
|
||||
|
||||
@attr(shard=2)
|
||||
@skip_unless_lms
|
||||
class CourseCohortDiscussionTopicsTestCase(CohortViewsTestCase):
|
||||
class CourseDividedDiscussionTopicsTestCase(CohortViewsTestCase):
|
||||
"""
|
||||
Tests the `cohort_discussion_topics` view.
|
||||
Tests the `divide_discussion_topics` view.
|
||||
"""
|
||||
|
||||
def test_non_staff(self):
|
||||
"""
|
||||
Verify that we cannot access cohort_discussion_topics if we're a non-staff user.
|
||||
Verify that we cannot access divide_discussion_topics if we're a non-staff user.
|
||||
"""
|
||||
self._verify_non_staff_cannot_access(cohort_discussion_topics, "GET", [unicode(self.course.id)])
|
||||
self._verify_non_staff_cannot_access(discussion_topics, "GET", [unicode(self.course.id)])
|
||||
|
||||
def test_get_discussion_topics(self):
|
||||
"""
|
||||
Verify that course_cohort_settings_handler is working for HTTP GET.
|
||||
Verify that divide_discussion_topics is working for HTTP GET.
|
||||
"""
|
||||
# create inline & course-wide discussion to verify the different map.
|
||||
self.create_cohorted_discussions()
|
||||
self.create_divided_discussions()
|
||||
|
||||
response = self.get_handler(self.course, handler=cohort_discussion_topics)
|
||||
response = self.get_handler(self.course, handler=discussion_topics)
|
||||
start_date = response['inline_discussions']['subcategories']['Chapter']['start_date']
|
||||
expected_response = {
|
||||
"course_wide_discussions": {
|
||||
|
||||
@@ -64,20 +64,29 @@ def unlink_cohort_partition_group(cohort):
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def _get_course_cohort_settings_representation(course, is_cohorted, course_discussion_settings):
|
||||
def _get_course_cohort_settings_representation(cohort_id, is_cohorted):
|
||||
"""
|
||||
Returns a JSON representation of a course cohort settings.
|
||||
"""
|
||||
return {
|
||||
'id': cohort_id,
|
||||
'is_cohorted': is_cohorted,
|
||||
}
|
||||
|
||||
|
||||
def _get_course_discussion_settings_representation(course, course_discussion_settings):
|
||||
"""
|
||||
Returns a JSON representation of a course discussion settings.
|
||||
"""
|
||||
divided_course_wide_discussions, divided_inline_discussions = get_divided_discussions(
|
||||
course, course_discussion_settings
|
||||
)
|
||||
|
||||
return {
|
||||
'id': course_discussion_settings.id,
|
||||
'is_cohorted': is_cohorted,
|
||||
'cohorted_inline_discussions': divided_inline_discussions,
|
||||
'cohorted_course_wide_discussions': divided_course_wide_discussions,
|
||||
'always_cohort_inline_discussions': course_discussion_settings.always_divide_inline_discussions,
|
||||
'divided_inline_discussions': divided_inline_discussions,
|
||||
'divided_course_wide_discussions': divided_course_wide_discussions,
|
||||
'always_divide_inline_discussions': course_discussion_settings.always_divide_inline_discussions,
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +126,61 @@ def get_divided_discussions(course, discussion_settings):
|
||||
return divided_course_wide_discussions, divided_inline_discussions
|
||||
|
||||
|
||||
@require_http_methods(("GET", "PATCH"))
|
||||
@ensure_csrf_cookie
|
||||
@expect_json
|
||||
@login_required
|
||||
def course_discussions_settings_handler(request, course_key_string):
|
||||
"""
|
||||
The restful handler for divided discussion setting requests. Requires JSON.
|
||||
This will raise 404 if user is not staff.
|
||||
GET
|
||||
Returns the JSON representation of divided discussion settings for the course.
|
||||
PATCH
|
||||
Updates the divided discussion settings for the course. Returns the JSON representation of updated settings.
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_key_string)
|
||||
course = get_course_with_access(request.user, 'staff', course_key)
|
||||
discussion_settings = get_course_discussion_settings(course_key)
|
||||
|
||||
if request.method == 'PATCH':
|
||||
divided_course_wide_discussions, divided_inline_discussions = get_divided_discussions(
|
||||
course, discussion_settings
|
||||
)
|
||||
|
||||
settings_to_change = {}
|
||||
|
||||
if 'divided_course_wide_discussions' in request.json or 'divided_inline_discussions' in request.json:
|
||||
divided_course_wide_discussions = request.json.get(
|
||||
'divided_course_wide_discussions', divided_course_wide_discussions
|
||||
)
|
||||
divided_inline_discussions = request.json.get(
|
||||
'divided_inline_discussions', divided_inline_discussions
|
||||
)
|
||||
settings_to_change['divided_discussions'] = divided_course_wide_discussions + divided_inline_discussions
|
||||
|
||||
if 'always_divide_inline_discussions' in request.json:
|
||||
settings_to_change['always_divide_inline_discussions'] = request.json.get(
|
||||
'always_divide_inline_discussions'
|
||||
)
|
||||
|
||||
if not settings_to_change:
|
||||
return JsonResponse({"error": unicode("Bad Request")}, 400)
|
||||
|
||||
try:
|
||||
if settings_to_change:
|
||||
discussion_settings = set_course_discussion_settings(course_key, **settings_to_change)
|
||||
|
||||
except ValueError as err:
|
||||
# Note: error message not translated because it is not exposed to the user (UI prevents this state).
|
||||
return JsonResponse({"error": unicode(err)}, 400)
|
||||
|
||||
return JsonResponse(_get_course_discussion_settings_representation(
|
||||
course,
|
||||
discussion_settings
|
||||
))
|
||||
|
||||
|
||||
@require_http_methods(("GET", "PATCH"))
|
||||
@ensure_csrf_cookie
|
||||
@expect_json
|
||||
@@ -131,51 +195,27 @@ def course_cohort_settings_handler(request, course_key_string):
|
||||
Updates the cohort settings for the course. Returns the JSON representation of updated settings.
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_key_string)
|
||||
# Although this course data is not used this method will return 404 is user is not staff
|
||||
course = get_course_with_access(request.user, 'staff', course_key)
|
||||
is_cohorted = cohorts.is_course_cohorted(course_key)
|
||||
discussion_settings = get_course_discussion_settings(course_key)
|
||||
|
||||
if request.method == 'PATCH':
|
||||
divided_course_wide_discussions, divided_inline_discussions = get_divided_discussions(
|
||||
course, discussion_settings
|
||||
)
|
||||
|
||||
settings_to_change = {}
|
||||
|
||||
if 'is_cohorted' in request.json:
|
||||
settings_to_change['is_cohorted'] = request.json.get('is_cohorted')
|
||||
|
||||
if 'cohorted_course_wide_discussions' in request.json or 'cohorted_inline_discussions' in request.json:
|
||||
divided_course_wide_discussions = request.json.get(
|
||||
'cohorted_course_wide_discussions', divided_course_wide_discussions
|
||||
)
|
||||
divided_inline_discussions = request.json.get(
|
||||
'cohorted_inline_discussions', divided_inline_discussions
|
||||
)
|
||||
settings_to_change['divided_discussions'] = divided_course_wide_discussions + divided_inline_discussions
|
||||
|
||||
if 'always_cohort_inline_discussions' in request.json:
|
||||
settings_to_change['always_divide_inline_discussions'] = request.json.get(
|
||||
'always_cohort_inline_discussions'
|
||||
)
|
||||
|
||||
if not settings_to_change:
|
||||
if 'is_cohorted' not in request.json:
|
||||
return JsonResponse({"error": unicode("Bad Request")}, 400)
|
||||
|
||||
is_cohorted = request.json.get('is_cohorted')
|
||||
try:
|
||||
if 'is_cohorted' in settings_to_change:
|
||||
is_cohorted = settings_to_change['is_cohorted']
|
||||
cohorts.set_course_cohorted(course_key, is_cohorted)
|
||||
del settings_to_change['is_cohorted']
|
||||
settings_to_change['division_scheme'] = CourseDiscussionSettings.COHORT if is_cohorted \
|
||||
else CourseDiscussionSettings.NONE
|
||||
if settings_to_change:
|
||||
discussion_settings = set_course_discussion_settings(course_key, **settings_to_change)
|
||||
cohorts.set_course_cohorted(course_key, is_cohorted)
|
||||
scheme = CourseDiscussionSettings.COHORT if is_cohorted else CourseDiscussionSettings.NONE
|
||||
scheme_settings = {'division_scheme': scheme}
|
||||
set_course_discussion_settings(course_key, **scheme_settings)
|
||||
except ValueError as err:
|
||||
# Note: error message not translated because it is not exposed to the user (UI prevents this state).
|
||||
return JsonResponse({"error": unicode(err)}, 400)
|
||||
|
||||
return JsonResponse(_get_course_cohort_settings_representation(course, is_cohorted, discussion_settings))
|
||||
return JsonResponse(_get_course_cohort_settings_representation(
|
||||
cohorts.get_course_cohort_id(course_key),
|
||||
cohorts.is_course_cohorted(course_key)
|
||||
))
|
||||
|
||||
|
||||
@require_http_methods(("GET", "PUT", "POST", "PATCH"))
|
||||
@@ -428,9 +468,9 @@ def debug_cohort_mgmt(request, course_key_string):
|
||||
|
||||
@expect_json
|
||||
@login_required
|
||||
def cohort_discussion_topics(request, course_key_string):
|
||||
def discussion_topics(request, course_key_string):
|
||||
"""
|
||||
The handler for cohort discussion categories requests.
|
||||
The handler for divided discussion categories requests.
|
||||
This will raise 404 if user is not staff.
|
||||
|
||||
Returns the JSON representation of discussion topics w.r.t categories for the course.
|
||||
|
||||
Reference in New Issue
Block a user