feat: Add Studio UI to edit discussions toggle [TNL-7349] (#29720)
Adds a UI in studio to toggle discussions for a units if a course is using a discussion provider that supports it and per-unit discussion control is enabled. Co-authored-by: Kshitij Sobti <kshitij@opencraft.com Co-authored-by: Agrendalath <piotr@surowiec.it>
This commit is contained in:
@@ -40,6 +40,7 @@ from common.djangoapps.util.date_utils import get_default_time_display
|
||||
from common.djangoapps.util.json_request import JsonResponse, expect_json
|
||||
from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService
|
||||
from openedx.core.djangoapps.bookmarks import api as bookmarks_api
|
||||
from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration
|
||||
from openedx.core.lib.gating import api as gating_api
|
||||
from openedx.core.lib.xblock_utils import hash_resource, request_token, wrap_xblock, wrap_xblock_aside
|
||||
from openedx.core.toggles import ENTRANCE_EXAMS
|
||||
@@ -1221,6 +1222,17 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
|
||||
'category': xblock.category,
|
||||
'has_children': xblock.has_children
|
||||
}
|
||||
|
||||
if xblock.category == 'course':
|
||||
discussions_config = DiscussionsConfiguration.get(course.id)
|
||||
show_unit_level_discussions_toggle = (
|
||||
discussions_config.enabled and
|
||||
discussions_config.supports_in_context_discussions() and
|
||||
discussions_config.enable_in_context and
|
||||
discussions_config.unit_level_visibility
|
||||
)
|
||||
xblock_info["unit_level_discussions"] = show_unit_level_discussions_toggle
|
||||
|
||||
if is_concise:
|
||||
if child_info and child_info.get('children', []):
|
||||
xblock_info['child_info'] = child_info
|
||||
|
||||
@@ -11,7 +11,7 @@ import Course from 'js/models/course';
|
||||
describe('CourseOutlinePage', function() {
|
||||
var createCourseOutlinePage, displayNameInput, model, outlinePage, requests, getItemsOfType, getItemHeaders,
|
||||
verifyItemsExpanded, expandItemsAndVerifyState, collapseItemsAndVerifyState, selectBasicSettings,
|
||||
selectVisibilitySettings, selectAdvancedSettings, createMockCourseJSON, createMockSectionJSON,
|
||||
selectVisibilitySettings, selectDiscussionSettings, selectAdvancedSettings, createMockCourseJSON, createMockSectionJSON,
|
||||
createMockSubsectionJSON, verifyTypePublishable, mockCourseJSON, mockEmptyCourseJSON, setSelfPaced, setSelfPacedCustomPLS,
|
||||
mockSingleSectionCourseJSON, createMockVerticalJSON, createMockIndexJSON, mockCourseEntranceExamJSON,
|
||||
selectOnboardingExam, createMockCourseJSONWithReviewRules,mockCourseJSONWithReviewRules,
|
||||
@@ -37,6 +37,7 @@ describe('CourseOutlinePage', function() {
|
||||
display_name: 'Section',
|
||||
children: []
|
||||
},
|
||||
unit_level_discussions: false,
|
||||
user_partitions: [],
|
||||
user_partition_info: {},
|
||||
highlights_enabled: true,
|
||||
@@ -197,6 +198,10 @@ describe('CourseOutlinePage', function() {
|
||||
$(".modal-section .settings-tab-button[data-tab='advanced']").click();
|
||||
};
|
||||
|
||||
function selectDiscussionSettings() {
|
||||
$(".modal-section .settings-tab-button[data-tab='discussion']").click();
|
||||
}
|
||||
|
||||
setSelfPaced = function() {
|
||||
/* global course */
|
||||
course.set('self_paced', true);
|
||||
@@ -300,7 +305,7 @@ describe('CourseOutlinePage', function() {
|
||||
'course-outline', 'xblock-string-field-editor', 'modal-button',
|
||||
'basic-modal', 'course-outline-modal', 'release-date-editor',
|
||||
'due-date-editor', 'self-paced-due-date-editor', 'grading-editor', 'publish-editor',
|
||||
'staff-lock-editor', 'unit-access-editor', 'content-visibility-editor',
|
||||
'staff-lock-editor', 'unit-access-editor', 'discussion-editor', 'content-visibility-editor',
|
||||
'settings-modal-tabs', 'timed-examination-preference-editor', 'access-editor',
|
||||
'show-correctness-editor', 'highlights-editor', 'highlights-enable-editor',
|
||||
'course-highlights-enable'
|
||||
@@ -2316,8 +2321,9 @@ describe('CourseOutlinePage', function() {
|
||||
|
||||
// Note: most tests for units can be found in Bok Choy
|
||||
describe('Unit', function() {
|
||||
var getUnitStatus = function(options) {
|
||||
mockCourseJSON = createMockCourseJSON({}, [
|
||||
var getUnitStatus = function(options, courseOptions) {
|
||||
courseOptions = courseOptions || {};
|
||||
mockCourseJSON = createMockCourseJSON(courseOptions, [
|
||||
createMockSectionJSON({}, [
|
||||
createMockSubsectionJSON({}, [
|
||||
createMockVerticalJSON(options)
|
||||
@@ -2409,6 +2415,35 @@ describe('CourseOutlinePage', function() {
|
||||
expect(messages).toContainText('Contains staff only content');
|
||||
});
|
||||
|
||||
describe('discussion settings', function () {
|
||||
it('hides discussion settings if unit level discussions are disabled', function() {
|
||||
getUnitStatus({}, {unit_level_discussions: false});
|
||||
outlinePage.$('.outline-unit .configure-button').click();
|
||||
expect($('.modal-section .edit-discussion')).not.toExist();
|
||||
});
|
||||
|
||||
it('shows discussion settings if unit level discussions are enabled', function() {
|
||||
getUnitStatus({}, {unit_level_discussions: true});
|
||||
outlinePage.$('.outline-unit .configure-button').click();
|
||||
expect($('.modal-section .edit-discussion')).toExist();
|
||||
});
|
||||
|
||||
it('marks checkbox as disabled', function() {
|
||||
getUnitStatus({}, {unit_level_discussions: true});
|
||||
outlinePage.$('.outline-unit .configure-button').click();
|
||||
|
||||
var discussionCheckbox = $('#discussion_enabled');
|
||||
expect(discussionCheckbox).toExist();
|
||||
expect(discussionCheckbox.is(':checked')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('marks checkbox as enabled', function() {
|
||||
getUnitStatus({discussion_enabled: true}, {unit_level_discussions: true});
|
||||
outlinePage.$('.outline-unit .configure-button').click();
|
||||
expect($('#discussion_enabled').is(':checked')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
verifyTypePublishable('unit', function(options) {
|
||||
return createMockCourseJSON({}, [
|
||||
createMockSectionJSON({}, [
|
||||
|
||||
@@ -160,6 +160,7 @@ define(['jquery', 'underscore', 'js/views/xblock_outline', 'common/js/components
|
||||
var modal;
|
||||
var enableProctoredExams = false;
|
||||
var enableTimedExams = false;
|
||||
var unitLevelDiscussions = false;
|
||||
if (this.model.get('category') === 'sequential') {
|
||||
if (this.parentView.parentView.model.has('enable_proctored_exams')) {
|
||||
enableProctoredExams = this.parentView.parentView.model.get('enable_proctored_exams');
|
||||
@@ -168,12 +169,15 @@ define(['jquery', 'underscore', 'js/views/xblock_outline', 'common/js/components
|
||||
enableTimedExams = this.parentView.parentView.model.get('enable_timed_exams');
|
||||
}
|
||||
}
|
||||
|
||||
if (this.model.get('category') === 'vertical') {
|
||||
unitLevelDiscussions = this.parentView.parentView.parentView.model.get('unit_level_discussions');
|
||||
}
|
||||
modal = CourseOutlineModalsFactory.getModal('edit', this.model, {
|
||||
onSave: this.refresh.bind(this),
|
||||
parentInfo: this.parentInfo,
|
||||
enable_proctored_exams: enableProctoredExams,
|
||||
enable_timed_exams: enableTimedExams,
|
||||
unit_level_discussions: unitLevelDiscussions,
|
||||
xblockType: XBlockViewUtils.getXBlockType(
|
||||
this.model.get('category'), this.parentView.model, true
|
||||
)
|
||||
|
||||
@@ -17,7 +17,8 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
AbstractEditor, BaseDateEditor,
|
||||
ReleaseDateEditor, DueDateEditor, SelfPacedDueDateEditor, GradingEditor, PublishEditor, AbstractVisibilityEditor,
|
||||
StaffLockEditor, UnitAccessEditor, ContentVisibilityEditor, TimedExaminationPreferenceEditor,
|
||||
AccessEditor, ShowCorrectnessEditor, HighlightsEditor, HighlightsEnableXBlockModal, HighlightsEnableEditor;
|
||||
AccessEditor, ShowCorrectnessEditor, HighlightsEditor, HighlightsEnableXBlockModal, HighlightsEnableEditor,
|
||||
DiscussionEditor;
|
||||
|
||||
CourseOutlineXBlockModal = BaseModal.extend({
|
||||
events: _.extend({}, BaseModal.prototype.events, {
|
||||
@@ -934,6 +935,50 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
}
|
||||
});
|
||||
|
||||
DiscussionEditor = AbstractEditor.extend({
|
||||
templateName: 'discussion-editor',
|
||||
className: 'edit-discussion',
|
||||
afterRender: function() {
|
||||
AbstractEditor.prototype.afterRender.call(this);
|
||||
this.setStatus(this.currentValue());
|
||||
},
|
||||
|
||||
currentValue: function() {
|
||||
var discussionEnabled = this.model.get('discussion_enabled');
|
||||
return discussionEnabled === true || discussionEnabled === 'enabled';
|
||||
},
|
||||
|
||||
setStatus: function(value) {
|
||||
this.$('#discussion_enabled').prop('checked', value);
|
||||
},
|
||||
|
||||
isEnabled: function() {
|
||||
return this.$('#discussion_enabled').is(':checked');
|
||||
},
|
||||
|
||||
hasChanges: function() {
|
||||
return this.currentValue() !== this.isEnabled();
|
||||
},
|
||||
|
||||
getRequestData: function() {
|
||||
if (this.hasChanges()) {
|
||||
return {
|
||||
publish: 'republish',
|
||||
metadata: {
|
||||
discussion_enabled: this.isEnabled()
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
getContext: function() {
|
||||
return {
|
||||
hasDiscussionEnabled: this.currentValue()
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
ContentVisibilityEditor = AbstractVisibilityEditor.extend({
|
||||
templateName: 'content-visibility-editor',
|
||||
className: 'edit-content-visibility',
|
||||
@@ -1169,7 +1214,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
editors: []
|
||||
};
|
||||
if (xblockInfo.isVertical()) {
|
||||
editors = [StaffLockEditor, UnitAccessEditor];
|
||||
editors = [StaffLockEditor, UnitAccessEditor, DiscussionEditor];
|
||||
} else {
|
||||
tabs = [
|
||||
{
|
||||
@@ -1215,6 +1260,10 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
});
|
||||
}
|
||||
|
||||
if (!options.unit_level_discussions) {
|
||||
editors = _.without(editors, DiscussionEditor);
|
||||
}
|
||||
|
||||
return new SettingsXBlockModal($.extend({
|
||||
tabs: tabs,
|
||||
editors: editors,
|
||||
|
||||
@@ -745,6 +745,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.edit-discussion,
|
||||
.edit-staff-lock,
|
||||
.edit-content-visibility,
|
||||
.edit-unit-access {
|
||||
@@ -756,7 +757,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
// UI: staff lock section
|
||||
// UI: staff lock and discussion
|
||||
.edit-discussion,
|
||||
.edit-staff-lock,
|
||||
.edit-settings-timed-examination,
|
||||
.edit-unit-access {
|
||||
@@ -828,6 +830,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.edit-discussion,
|
||||
.edit-unit-access,
|
||||
.edit-staff-lock {
|
||||
.modal-section-content {
|
||||
@@ -869,6 +872,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.edit-discussion,
|
||||
.edit-unit-access,
|
||||
.edit-staff-lock {
|
||||
.modal-section-content {
|
||||
|
||||
@@ -29,7 +29,7 @@ from django.urls import reverse
|
||||
|
||||
<%block name="header_extras">
|
||||
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
|
||||
% for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', 'due-date-editor', 'self-paced-due-date-editor', 'release-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'unit-access-editor', 'content-visibility-editor', 'verification-access-editor', 'timed-examination-preference-editor', 'access-editor', 'settings-modal-tabs', 'show-correctness-editor', 'highlights-editor', 'highlights-enable-editor', 'course-highlights-enable']:
|
||||
% for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', 'due-date-editor', 'self-paced-due-date-editor', 'release-date-editor', 'grading-editor', 'publish-editor', 'staff-lock-editor', 'unit-access-editor', 'discussion-editor', 'content-visibility-editor', 'verification-access-editor', 'timed-examination-preference-editor', 'access-editor', 'settings-modal-tabs', 'show-correctness-editor', 'highlights-editor', 'highlights-enable-editor', 'course-highlights-enable']:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="js/${template_name}.underscore" />
|
||||
</script>
|
||||
|
||||
11
cms/templates/js/discussion-editor.underscore
Normal file
11
cms/templates/js/discussion-editor.underscore
Normal file
@@ -0,0 +1,11 @@
|
||||
<form>
|
||||
<h3 class="modal-section-title">
|
||||
<%- gettext('Discussion') %>
|
||||
</h3>
|
||||
<div class="modal-section-content discussion-enabled">
|
||||
<label class="label">
|
||||
<input type="checkbox" id="discussion_enabled" name="discussion_enabled" class="input input-checkbox" />
|
||||
<%- gettext('Enable discussion') %>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
@@ -3,7 +3,7 @@ var userPartitionInfo = xblockInfo.get('user_partition_info');
|
||||
var selectablePartitions = userPartitionInfo['selectable_partitions'];
|
||||
%>
|
||||
<form>
|
||||
<% if (selectablePartitions.length > 0) { %>
|
||||
<% if (selectablePartitions && selectablePartitions.length > 0) { %>
|
||||
<h3 class="modal-section-title access-change">
|
||||
<%- gettext('Unit Access') %>
|
||||
</h3>
|
||||
|
||||
Reference in New Issue
Block a user