Commit fd7ac216 by Albert St. Aubin

Working publisher/subscriber events for Cohorts and discussions

parent 3abc7dc0
......@@ -239,7 +239,7 @@ FEATURES = {
'ALLOW_PUBLIC_ACCOUNT_CREATION': True,
# Whether or not the dynamic EnrollmentTrackUserPartition should be registered.
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION': False,
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION': True,
}
ENABLE_JASMINE = False
......
......@@ -56,6 +56,12 @@ class InstructorDashboardPage(CoursePage):
discussion_management_section.wait_for_page()
return discussion_management_section
def is_discussion_management_visible(self):
"""
Is the Discussion tab visible
"""
return self.q(css='[data-section="discussions_management"').visible
def select_data_download(self):
"""
Selects the data download tab and returns a DataDownloadPage.
......
......@@ -310,6 +310,7 @@ class DivisionSchemeTest(BaseDividedDiscussionTest, BaseDiscussionMixin):
Go to the discussion tab on the instructor dashboard.
"""
self.instructor_dashboard_page.visit()
self.assertTrue(self.instructor_dashboard_page.is_discussion_management_visible())
self.instructor_dashboard_page.select_discussion_management()
self.discussion_management_page.wait_for_page()
......@@ -357,11 +358,55 @@ class DivisionSchemeTest(BaseDividedDiscussionTest, BaseDiscussionMixin):
def test_disabling_cohorts(self):
"""
Test that disabling cohorts hides the cohort division scheme iff it is not the selected scheme
Test that the discussions management tab hides when there is <= 1 enrollment track, the Cohort division scheme
is not selected, and cohorts are disabled.
(even without reloading the page).
"""
# TODO: will be added as part of AJ's work.
pass
self.disable_cohorting(self.course_fixture)
self.instructor_dashboard_page.visit()
self.assertFalse(self.instructor_dashboard_page.is_discussion_management_visible())
def test_disabling_cohorts_while_selected(self):
"""
Test that disabling cohorts does not hide the discussion tab when there is more than one enrollment track.
Also that the division scheme for cohorts is visible iff it was selected.
(even without reloading the page).
"""
add_enrollment_course_modes(self.browser, self.course_id, ['audit', 'verified'])
# Verify that the tab is visible, the cohort scheme is selected by default for divided discussions
self.disable_cohorting(self.course_fixture)
# Go to Discussions tab and ensure that the correct scheme options are visible
self.view_discussion_management_page()
self.assertTrue(
self.discussion_management_page.division_scheme_visible(
self.discussion_management_page.COHORT_SCHEME
)
)
def test_disabling_cohorts_while_not_selected(self):
"""
Test that disabling cohorts does not hide the discussion tab when there is more than one enrollment track.
Also that the division scheme for cohorts is not visible when cohorts are disabled and another scheme is
selected for division.
(even without reloading the page).
"""
add_enrollment_course_modes(self.browser, self.course_id, ['audit', 'verified'])
# Verify that the tab is visible
self.view_discussion_management_page()
self.discussion_management_page.select_division_scheme(self.discussion_management_page.ENROLLMENT_TRACK_SCHEME)
self.verify_save_confirmation_message(self.scheme_key)
self.disable_cohorting(self.course_fixture)
# Go to Discussions tab and ensure that the correct scheme options are visible
self.view_discussion_management_page()
self.assertFalse(
self.discussion_management_page.division_scheme_visible(
self.discussion_management_page.COHORT_SCHEME
)
)
def test_single_enrollment_mode(self):
"""
......
......@@ -868,11 +868,22 @@ def available_division_schemes(course_key):
available_schemes = []
if is_course_cohorted(course_key):
available_schemes.append(CourseDiscussionSettings.COHORT)
if len(_get_enrollment_track_groups(course_key)) > 1:
if enrollment_track_group_count(course_key) > 1:
available_schemes.append(CourseDiscussionSettings.ENROLLMENT_TRACK)
return available_schemes
def enrollment_track_group_count(course_key):
"""
Returns the count of possible enrollment track division schemes for this course.
Args:
course_key: CourseKey
Returns:
Count of enrollment track division scheme
"""
return len(_get_enrollment_track_groups(course_key))
def _get_course_division_scheme(course_discussion_settings):
division_scheme = course_discussion_settings.division_scheme
if (
......@@ -882,7 +893,7 @@ def _get_course_division_scheme(course_discussion_settings):
division_scheme = CourseDiscussionSettings.NONE
elif (
division_scheme == CourseDiscussionSettings.ENROLLMENT_TRACK and
len(_get_enrollment_track_groups(course_discussion_settings.course_id)) <= 1
enrollment_track_group_count(course_discussion_settings.course_id) <= 1
):
division_scheme = CourseDiscussionSettings.NONE
return division_scheme
......
......@@ -30,7 +30,7 @@ class TestECommerceDashboardViews(SiteMixin, SharedModuleStoreTestCase):
# URL for instructor dash
cls.url = reverse('instructor_dashboard', kwargs={'course_id': cls.course.id.to_deprecated_string()})
cls.ecommerce_link = '<button type="button" class="btn-link" data-section="e-commerce">E-Commerce</button>'
cls.ecommerce_link = '<button type="button" class="btn-link e-commerce" data-section="e-commerce">E-Commerce</button>'
def setUp(self):
super(TestECommerceDashboardViews, self).setUp()
......
......@@ -31,7 +31,7 @@ class TestNewInstructorDashboardEmailViewMongoBacked(SharedModuleStoreTestCase):
# URL for instructor dash
cls.url = reverse('instructor_dashboard', kwargs={'course_id': cls.course.id.to_deprecated_string()})
# URL for email view
cls.email_link = '<button type="button" class="btn-link" data-section="send_email">Email</button>'
cls.email_link = '<button type="button" class="btn-link send_email" data-section="send_email">Email</button>'
def setUp(self):
super(TestNewInstructorDashboardEmailViewMongoBacked, self).setUp()
......@@ -126,7 +126,7 @@ class TestNewInstructorDashboardEmailViewXMLBacked(SharedModuleStoreTestCase):
# URL for instructor dash
cls.url = reverse('instructor_dashboard', kwargs={'course_id': cls.course_key.to_deprecated_string()})
# URL for email view
cls.email_link = '<button type="button" class="btn-link" data-section="send_email">Email</button>'
cls.email_link = '<button type="button" class="btn-link send_email" data-section="send_email">Email</button>'
def setUp(self):
super(TestNewInstructorDashboardEmailViewXMLBacked, self).setUp()
......@@ -138,7 +138,7 @@ class TestNewInstructorDashboardEmailViewXMLBacked(SharedModuleStoreTestCase):
# URL for instructor dash
self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course_key.to_deprecated_string()})
# URL for email view
self.email_link = '<button type="button" class="btn-link" data-section="send_email">Email</button>'
self.email_link = '<button type="button" class="btn-link send_email" data-section="send_email">Email</button>'
def tearDown(self):
super(TestNewInstructorDashboardEmailViewXMLBacked, self).tearDown()
......
......@@ -27,7 +27,7 @@ class TestProctoringDashboardViews(SharedModuleStoreTestCase):
# URL for instructor dash
cls.url = reverse('instructor_dashboard', kwargs={'course_id': cls.course.id.to_deprecated_string()})
button = '<button type="button" class="btn-link" data-section="special_exams">Special Exams</button>'
button = '<button type="button" class="btn-link special_exams" data-section="special_exams">Special Exams</button>'
cls.proctoring_link = button
def setUp(self):
......
......@@ -228,7 +228,7 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT
Test analytics dashboard message is shown
"""
response = self.client.get(self.url)
analytics_section = '<li class="nav-item"><button type="button" class="btn-link" data-section="instructor_analytics">Analytics</button></li>' # pylint: disable=line-too-long
analytics_section = '<li class="nav-item"><button type="button" class="btn-link instructor_analytics" data-section="instructor_analytics">Analytics</button></li>' # pylint: disable=line-too-long
self.assertIn(analytics_section, response.content)
# link to dashboard shown
......@@ -327,7 +327,7 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT
"""
ora_section = (
'<li class="nav-item">'
'<button type="button" class="btn-link" data-section="open_response_assessment">'
'<button type="button" class="btn-link open_response_assessment" data-section="open_response_assessment">'
'Open Responses'
'</button>'
'</li>'
......
......@@ -31,8 +31,8 @@ from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
from courseware.access import has_access
from courseware.courses import get_course_by_id, get_studio_url
from django_comment_client.utils import has_forum_access
from django_comment_common.models import FORUM_ROLE_ADMINISTRATOR
from django_comment_client.utils import has_forum_access, available_division_schemes, enrollment_track_group_count
from django_comment_common.models import FORUM_ROLE_ADMINISTRATOR, CourseDiscussionSettings
from openedx.core.djangoapps.course_groups.cohorts import get_course_cohorts, is_course_cohorted, DEFAULT_COHORT_NAME
from student.models import CourseEnrollment
from shoppingcart.models import Coupon, PaidCourseRegistration, CourseRegCodeItem
......@@ -524,9 +524,13 @@ def _section_cohort_management(course, access):
def _section_discussions_management(course, access):
""" Provide data for the corresponding discussion management section """
course_key = course.id
enrollment_track_schemes = available_division_schemes(course_key)
section_data = {
'section_key': 'discussions_management',
'section_display_name': _('Discussions'),
'is_hidden': (not is_course_cohorted(course_key) and
CourseDiscussionSettings.ENROLLMENT_TRACK not in enrollment_track_schemes),
'enrollment_track_count': enrollment_track_group_count(course_key),
'discussion_topics_url': reverse('discussion_topics', kwargs={'course_key_string': unicode(course_key)}),
'course_discussion_settings': reverse(
'course_discussions_settings',
......
......@@ -379,7 +379,7 @@ FEATURES = {
'ENABLE_COOKIE_CONSENT': False,
# Whether or not the dynamic EnrollmentTrackUserPartition should be registered.
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION': False,
'ENABLE_ENROLLMENT_TRACK_USER_PARTITION': True,
# Enable one click program purchase
# See LEARNER-493
......
......@@ -5,27 +5,34 @@
'js/discussions_management/views/divided_discussions_course_wide',
'edx-ui-toolkit/js/utils/html-utils',
'edx-ui-toolkit/js/utils/string-utils',
'js/views/base_dashboard_view',
'js/models/notification',
'js/views/notification'
],
function($, _, Backbone, gettext, InlineDiscussionsView, CourseWideDiscussionsView, HtmlUtils, StringUtils) {
function($, _, Backbone, gettext, InlineDiscussionsView,
CourseWideDiscussionsView,
HtmlUtils, StringUtils, BaseDashboardView) {
/* global NotificationModel, NotificationView */
var hiddenClass = 'is-hidden';
var cohort = 'cohort';
var none = 'none';
var enrollmentTrack = 'enrollment_track';
var HIDDEN_CLASS = 'hidden';
var TWO_COLUMN_CLASS = 'two-column-layout';
var THREE_COLUMN_CLASS = 'three-column-layout';
var COHORT = 'cohort';
var NONE = 'none';
var ENROLLMENT_TRACK = 'enrollment_track';
var DiscussionsView = Backbone.View.extend({
var DiscussionsView = BaseDashboardView.extend({
events: {
'click .division-scheme': 'divisionSchemeChanged'
'click .division-scheme': 'divisionSchemeChanged',
'change .cohorts-state': 'onCohortsEnabledChanged'
},
initialize: function(options) {
this.template = HtmlUtils.template($('#discussions-tpl').text());
this.context = options.context;
this.discussionSettings = options.discussionSettings;
this.listenTo(this.pubSub, 'cohorts:state', this.cohortStateUpdate, this);
},
render: function() {
......@@ -42,25 +49,25 @@
getDivisionSchemeData: function(selectedScheme) {
return [
{
key: none,
key: NONE,
displayName: gettext('Not divided'),
descriptiveText: gettext('Discussions are unified; all learners interact with posts from other learners, regardless of the group they are in.'), // eslint-disable-line max-len
selected: selectedScheme === none,
selected: selectedScheme === NONE,
enabled: true // always leave none enabled
},
{
key: enrollmentTrack,
key: ENROLLMENT_TRACK,
displayName: gettext('Enrollment Tracks'),
descriptiveText: gettext('Use enrollment tracks as the basis for dividing discussions. All learners, regardless of their enrollment track, see the same discussion topics, but within divided topics, only learners who are in the same enrollment track see and respond to each others’ posts.'), // eslint-disable-line max-len
selected: selectedScheme === enrollmentTrack,
enabled: this.isSchemeAvailable(enrollmentTrack) || selectedScheme === enrollmentTrack
selected: selectedScheme === ENROLLMENT_TRACK,
enabled: this.isSchemeAvailable(ENROLLMENT_TRACK) || selectedScheme === ENROLLMENT_TRACK
},
{
key: cohort,
key: COHORT,
displayName: gettext('Cohorts'),
descriptiveText: gettext('Use cohorts as the basis for dividing discussions. All learners, regardless of cohort, see the same discussion topics, but within divided topics, only members of the same cohort see and respond to each others’ posts. '), // eslint-disable-line max-len
selected: selectedScheme === cohort,
enabled: this.isSchemeAvailable(cohort) || selectedScheme === cohort
selected: selectedScheme === COHORT,
enabled: this.isSchemeAvailable(COHORT) || selectedScheme === COHORT
}
];
......@@ -80,6 +87,42 @@
this.notification.render();
},
cohortStateUpdate: function(state) {
if ($('.discussions-management').data('enrollment-track-count') <= 1) {
this.showDiscussionManagement(state.is_cohorted);
} if (this.getSelectedScheme() !== COHORT) {
this.showCohortSchemeControl(state.is_cohorted);
}
},
showDiscussionManagement: function(show) {
if (!show) {
$('.btn-link.discussions_management').addClass(HIDDEN_CLASS);
$('#discussions_management').addClass(HIDDEN_CLASS);
} else {
$('.btn-link.discussions_management').removeClass(HIDDEN_CLASS);
$('#discussions_management').removeClass(HIDDEN_CLASS);
}
},
showCohortSchemeControl: function(show) {
if (!show) {
$('.division-scheme-item.cohort').addClass(HIDDEN_CLASS);
this.updateSchemeSelectionLayout(2);
} else {
$('.division-scheme-item.cohort').removeClass(HIDDEN_CLASS);
this.updateSchemeSelectionLayout(3);
}
},
updateSchemeSelectionLayout: function(columns) {
if (columns === 2) {
$('.division-scheme-item').removeClass(THREE_COLUMN_CLASS).addClass(TWO_COLUMN_CLASS);
} else {
$('.division-scheme-item').removeClass(TWO_COLUMN_CLASS).addClass(THREE_COLUMN_CLASS);
}
},
removeNotification: function() {
if (this.notification) {
this.notification.remove();
......@@ -120,13 +163,13 @@
fieldData, {patch: true, wait: true}
).done(function() {
switch (selectedScheme) {
case none:
case NONE:
details = gettext('Discussion topics in the course are not divided.');
break;
case enrollmentTrack:
case ENROLLMENT_TRACK:
details = gettext('Any divided discussion topics are divided based on enrollment track.'); // eslint-disable-line max-len
break;
case cohort:
case COHORT:
details = gettext('Any divided discussion topics are divided based on cohort.');
break;
default:
......@@ -156,10 +199,10 @@
},
updateTopicVisibility: function(selectedScheme, topicNav) {
if (selectedScheme === none) {
topicNav.addClass(hiddenClass);
if (selectedScheme === NONE) {
topicNav.addClass(HIDDEN_CLASS);
} else {
topicNav.removeClass(hiddenClass);
topicNav.removeClass(HIDDEN_CLASS);
}
},
......
(function(define) {
'use strict';
define(['jquery', 'underscore', 'backbone', 'gettext', 'js/groups/models/cohort',
'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/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, VerifiedTrackSettingsNotificationView, HtmlUtils) {
var hiddenClass = 'is-hidden',
'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/verified_track_settings_notification',
'edx-ui-toolkit/js/utils/html-utils',
'js/views/base_dashboard_view',
'js/views/file_uploader', 'js/models/notification', 'js/views/notification',
'string_utils'],
function($, _, Backbone, gettext, CohortModel,
VerifiedTrackSettingsModel,
CohortEditorView, CohortFormView,
CourseCohortSettingsNotificationView,
VerifiedTrackSettingsNotificationView, HtmlUtils, BaseDashboardView) {
var hiddenClass = 'hidden',
disabledClass = 'is-disabled',
enableCohortsSelector = '.cohorts-state';
var CohortsView = Backbone.View.extend({
var CohortsView = BaseDashboardView.extend({
events: {
'change .cohort-select': 'onCohortSelected',
'change .cohorts-state': 'onCohortsEnabledChanged',
......@@ -27,7 +31,6 @@
initialize: function(options) {
var model = this.model;
this.template = HtmlUtils.template($('#cohorts-tpl').text());
this.selectorTemplate = HtmlUtils.template($('#cohort-selector-tpl').text());
this.context = options.context;
......@@ -151,6 +154,7 @@
).done(function() {
self.render();
self.renderCourseCohortSettingsNotificationView();
self.pubSub.trigger('cohorts:state', fieldData);
}).fail(function(result) {
self.showNotification({
type: 'error',
......
......@@ -276,7 +276,7 @@ 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');
expect(cohortsView.$('.wrapper-cohort-supplemental')).toHaveClass('hidden');
});
it('syncs data when membership tab is clicked', function() {
......@@ -294,7 +294,7 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
createCohortsView(this);
// Should see the control to toggle CSV file upload.
expect(cohortsView.$('.wrapper-cohort-supplemental')).not.toHaveClass('is-hidden');
expect(cohortsView.$('.wrapper-cohort-supplemental')).not.toHaveClass('hidden');
// But upload form should not be visible until toggle is clicked.
expect(cohortsView.$(fileUploadFormCss).length).toBe(0);
uploadCsvToggle = cohortsView.$('.toggle-cohort-management-secondary');
......@@ -302,7 +302,7 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
toContain('Assign students to cohorts by uploading a CSV file');
uploadCsvToggle.click();
// After toggle is clicked, it should be hidden.
expect(uploadCsvToggle).toHaveClass('is-hidden');
expect(uploadCsvToggle).toHaveClass('hidden');
fileUploadForm = cohortsView.$(fileUploadFormCss);
expect(fileUploadForm.length).toBe(1);
......@@ -517,7 +517,7 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
cohortsView.$('.action-create').click();
expect(cohortsView.$('.cohort-management-settings-form').length).toBe(1);
expect(cohortsView.$('.cohort-management-nav')).toHaveClass('is-disabled');
expect(cohortsView.$('.cohort-management-group')).toHaveClass('is-hidden');
expect(cohortsView.$('.cohort-management-group')).toHaveClass('hidden');
cohortsView.$('.cohort-name').val(defaultCohortName);
cohortsView.$('.type-random').prop('checked', true).change();
selectContentGroup(contentGroupId, MOCK_COHORTED_USER_PARTITION_ID);
......@@ -544,7 +544,7 @@ define(['backbone', 'jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers
);
verifyHeader(1, defaultCohortName, 0, MOCK_RANDOM_ASSIGNMENT);
expect(cohortsView.$('.cohort-management-nav')).not.toHaveClass('is-disabled');
expect(cohortsView.$('.cohort-management-group')).not.toHaveClass('is-hidden');
expect(cohortsView.$('.cohort-management-group')).not.toHaveClass('hidden');
expect(getAddModal().find('.cohort-management-settings-form').length).toBe(0);
});
......
(function(define) {
'use strict';
define(['jquery', 'backbone'],
function($, Backbone) {
// This Base view is useful when eventing or other features are shared between two or more
// views. Included with this view in the pubSub object allowing for events to be triggered
// and shared with other views.
var BaseDashboardView = Backbone.View.extend({
pubSub: $.extend({}, Backbone.Events)
});
return BaseDashboardView;
});
}).call(this, define || RequireJS.define);
......@@ -35,7 +35,7 @@
<!-- Uploading a CSV file of cohort assignments. -->
<button class="toggle-cohort-management-secondary" data-href="#cohort-management-file-upload"><%- gettext('Assign students to cohorts by uploading a CSV file') %></button>
<div class="cohort-management-file-upload csv-upload is-hidden" id="cohort-management-file-upload" tabindex="-1"></div>
<div class="cohort-management-file-upload csv-upload hidden" id="cohort-management-file-upload" tabindex="-1"></div>
<div class="cohort-management-supplemental">
<p class="">
......
......@@ -4,7 +4,7 @@
<div class="division-scheme-container">
<div class="division-scheme-items" role="group" aria-labelledby="division-scheme-title">
<% for (var i = 0; i < availableSchemes.length; i++) { %>
<div class="division-scheme-item <%- layoutClass %> <% if (!availableSchemes[i].enabled) { %>is-hidden<% } %>">
<div class="division-scheme-item <%- availableSchemes[i].key %> <%- layoutClass %> <% if (!availableSchemes[i].enabled) { %>hidden<% } %>">
<label class="division-scheme-label">
<input class="division-scheme <%- availableSchemes[i].key %>" type="radio" name="division-scheme"
value="<%- availableSchemes[i].key %>" aria-describedby="<%- availableSchemes[i].key %>-description"
......
......@@ -12,6 +12,7 @@ from openedx.core.djangoapps.course_groups.partition_scheme import get_cohorted_
<div class="discussions-management"
data-discussion-topics-url="${section_data['discussion_topics_url']}"
data-course-discussion-settings-url="${section_data['course_discussion_settings']}"
data-enrollment-track-count="${section_data['enrollment_track_count']}"
>
</div>
......
......@@ -121,9 +121,10 @@ from openedx.core.djangolib.markup import HTML
## when the javascript loads, it clicks on the first section
<ul class="instructor-nav">
% for section_data in sections:
<% is_hidden = section_data.get('is_hidden', False) %>
## This is necessary so we don't scrape 'section_display_name' as a string.
<% dname = section_data['section_display_name'] %>
<li class="nav-item"><button type="button" class="btn-link" data-section="${ section_data['section_key'] }">${_(dname)}</button></li>
<li class="nav-item"><button type="button" class="btn-link ${ section_data['section_key'] }${' hidden' if is_hidden else ''}" data-section="${ section_data['section_key'] }">${_(dname)}</button></li>
% endfor
</ul>
......@@ -131,7 +132,8 @@ from openedx.core.djangolib.markup import HTML
## to keep this short, sections can be pulled out into their own files
% for section_data in sections:
<section id="${ section_data['section_key'] }" class="idash-section" aria-labelledby="header-${section_data['section_key']}">
<% is_hidden = section_data.get('is_hidden', False) %>
<section id="${ section_data['section_key'] }" class="idash-section${' hidden' if hidden else ''}" aria-labelledby="header-${section_data['section_key']}">
<h3 class="hd hd-3" id="header-${ section_data['section_key'] }">${ section_data['section_display_name'] }</h3>
<%include file="${ section_data['section_key'] }.html" args="section_data=section_data" />
</section>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment