Merge remote-tracking branch 'upstream/master' into proversity/add-recover-password-endpoint [ci skip]
This commit is contained in:
@@ -1188,11 +1188,17 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
|
||||
xblock_info.update({
|
||||
'hide_after_due': xblock.hide_after_due,
|
||||
})
|
||||
elif xblock.category == 'chapter':
|
||||
elif xblock.category in ('chapter', 'course'):
|
||||
if xblock.category == 'chapter':
|
||||
xblock_info.update({
|
||||
'highlights': xblock.highlights,
|
||||
})
|
||||
elif xblock.category == 'course':
|
||||
xblock_info.update({
|
||||
'highlights_enabled_for_messaging': course.highlights_enabled_for_messaging,
|
||||
})
|
||||
xblock_info.update({
|
||||
'highlights': xblock.highlights,
|
||||
'highlights_enabled': highlights_setting.is_enabled(),
|
||||
'highlights_enabled_for_messaging': course.highlights_enabled_for_messaging,
|
||||
'highlights_preview_only': not COURSE_UPDATE_WAFFLE_FLAG.is_enabled(course.id),
|
||||
'highlights_doc_url': HelpUrlExpert.the_one().url_for_token('content_highlights'),
|
||||
})
|
||||
|
||||
@@ -2577,8 +2577,10 @@ class TestXBlockInfo(ItemTest):
|
||||
self.store.update_item(self.course, None)
|
||||
chapter = self.store.get_item(self.chapter.location)
|
||||
with highlights_setting.override():
|
||||
xblock_info = create_xblock_info(chapter)
|
||||
self.assertTrue(xblock_info['highlights_enabled'])
|
||||
chapter_xblock_info = create_xblock_info(chapter)
|
||||
course_xblock_info = create_xblock_info(self.course)
|
||||
self.assertTrue(chapter_xblock_info['highlights_enabled'])
|
||||
self.assertTrue(course_xblock_info['highlights_enabled_for_messaging'])
|
||||
|
||||
def validate_course_xblock_info(self, xblock_info, has_child_info=True, course_outline=False):
|
||||
"""
|
||||
@@ -2588,6 +2590,7 @@ class TestXBlockInfo(ItemTest):
|
||||
self.assertEqual(xblock_info['id'], unicode(self.course.location))
|
||||
self.assertEqual(xblock_info['display_name'], self.course.display_name)
|
||||
self.assertTrue(xblock_info['published'])
|
||||
self.assertFalse(xblock_info['highlights_enabled_for_messaging'])
|
||||
|
||||
# Finally, validate the entire response for consistency
|
||||
self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info, course_outline=course_outline)
|
||||
@@ -2608,7 +2611,6 @@ class TestXBlockInfo(ItemTest):
|
||||
self.assertEqual(xblock_info['format'], None)
|
||||
self.assertEqual(xblock_info['highlights'], self.chapter.highlights)
|
||||
self.assertFalse(xblock_info['highlights_enabled'])
|
||||
self.assertFalse(xblock_info['highlights_enabled_for_messaging'])
|
||||
|
||||
# Finally, validate the entire response for consistency
|
||||
self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info)
|
||||
|
||||
@@ -165,6 +165,7 @@ function(Backbone, _, str, ModuleUtils) {
|
||||
*/
|
||||
highlights: [],
|
||||
highlights_enabled: false,
|
||||
highlights_enabled_for_messaging: false,
|
||||
highlights_preview_only: true,
|
||||
highlights_doc_url: ''
|
||||
},
|
||||
|
||||
@@ -32,7 +32,9 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
|
||||
children: []
|
||||
},
|
||||
user_partitions: [],
|
||||
user_partition_info: {}
|
||||
user_partition_info: {},
|
||||
highlights_enabled: true,
|
||||
highlights_enabled_for_messaging: false
|
||||
}, options, {child_info: {children: children}});
|
||||
};
|
||||
|
||||
@@ -262,7 +264,8 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
|
||||
'due-date-editor', 'grading-editor', 'publish-editor',
|
||||
'staff-lock-editor', 'unit-access-editor', 'content-visibility-editor',
|
||||
'settings-modal-tabs', 'timed-examination-preference-editor', 'access-editor',
|
||||
'show-correctness-editor', 'highlights-editor'
|
||||
'show-correctness-editor', 'highlights-editor', 'highlights-enable-editor',
|
||||
'course-highlights-enable'
|
||||
]);
|
||||
appendSetFixtures(mockOutlinePage);
|
||||
mockCourseJSON = createMockCourseJSON({}, [
|
||||
@@ -529,20 +532,17 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
|
||||
});
|
||||
});
|
||||
|
||||
describe('Section Highlights', function() {
|
||||
var createCourse, createCourseWithHighlights, createCourseWithHighlightsDisabled, mockHighlightValues,
|
||||
highlightsLink, highlightInputs, openHighlights, saveHighlights, setHighlights,
|
||||
expectHighlightLinkNumberToBe, expectHighlightsToBe, expectServerHandshakeWithHighlights,
|
||||
expectHighlightsToUpdate,
|
||||
maxNumHighlights = 5;
|
||||
describe('Content Highlights', function() {
|
||||
var createCourse, createCourseWithHighlights, createCourseWithHighlightsDisabled,
|
||||
clickSaveOnModal, clickCancelOnModal;
|
||||
|
||||
beforeEach(function() {
|
||||
setSelfPaced();
|
||||
});
|
||||
|
||||
createCourse = function(sectionOptions) {
|
||||
createCourse = function(sectionOptions, courseOptions) {
|
||||
createCourseOutlinePage(this,
|
||||
createMockCourseJSON({}, [
|
||||
createMockCourseJSON(courseOptions, [
|
||||
createMockSectionJSON(sectionOptions)
|
||||
])
|
||||
);
|
||||
@@ -553,142 +553,256 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
|
||||
};
|
||||
|
||||
createCourseWithHighlightsDisabled = function() {
|
||||
createCourse({highlights_enabled: false});
|
||||
var highlightsDisabled = {highlights_enabled: false};
|
||||
createCourse(highlightsDisabled, highlightsDisabled);
|
||||
};
|
||||
|
||||
mockHighlightValues = function(numberOfHighlights) {
|
||||
var highlights = [],
|
||||
i;
|
||||
for (i = 0; i < numberOfHighlights; i++) {
|
||||
highlights.push('Highlight' + (i + 1));
|
||||
}
|
||||
return highlights;
|
||||
};
|
||||
|
||||
highlightsLink = function() {
|
||||
return outlinePage.$('.section-status >> .highlights-button');
|
||||
};
|
||||
|
||||
highlightInputs = function() {
|
||||
return $('.highlight-input-text');
|
||||
};
|
||||
|
||||
openHighlights = function() {
|
||||
highlightsLink().click();
|
||||
};
|
||||
|
||||
saveHighlights = function() {
|
||||
clickSaveOnModal = function() {
|
||||
$('.wrapper-modal-window .action-save').click();
|
||||
};
|
||||
|
||||
setHighlights = function(highlights) {
|
||||
var i;
|
||||
for (i = 0; i < highlights.length; i++) {
|
||||
$(highlightInputs()[i]).val(highlights[i]);
|
||||
}
|
||||
for (i = highlights.length; i < maxNumHighlights; i++) {
|
||||
$(highlightInputs()[i]).val('');
|
||||
}
|
||||
clickCancelOnModal = function() {
|
||||
$('.wrapper-modal-window .action-cancel').click();
|
||||
};
|
||||
|
||||
expectHighlightLinkNumberToBe = function(expectedNumber) {
|
||||
var link = highlightsLink();
|
||||
expect(link).toContainText('Section Highlights');
|
||||
expect(link.find('.number-highlights')).toHaveHtml(expectedNumber);
|
||||
};
|
||||
describe('Course Highlights Setting', function() {
|
||||
var highlightsSetting, expectHighlightsEnabledToBe, expectServerHandshake, openHighlightsSettings;
|
||||
|
||||
expectHighlightsToBe = function(expectedHighlights) {
|
||||
var highlights = highlightInputs(),
|
||||
i;
|
||||
highlightsSetting = function() {
|
||||
return $('.course-highlights-setting');
|
||||
};
|
||||
|
||||
expect(highlights).toHaveLength(maxNumHighlights);
|
||||
|
||||
for (i = 0; i < expectedHighlights.length; i++) {
|
||||
expect(highlights[i]).toHaveValue(expectedHighlights[i]);
|
||||
}
|
||||
for (i = expectedHighlights.length; i < maxNumHighlights; i++) {
|
||||
expect(highlights[i]).toHaveValue('');
|
||||
expect(highlights[i]).toHaveAttr('placeholder', 'A highlight to look forward to this week.');
|
||||
}
|
||||
};
|
||||
|
||||
expectServerHandshakeWithHighlights = function(highlights) {
|
||||
// POST to update section
|
||||
AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/mock-section', {
|
||||
publish: 'republish',
|
||||
metadata: {
|
||||
highlights: highlights
|
||||
expectHighlightsEnabledToBe = function(expectedEnabled) {
|
||||
if (expectedEnabled) {
|
||||
expect('.status-highlights-enabled-value.button').not.toExist();
|
||||
expect('.status-highlights-enabled-value.text').toExist();
|
||||
} else {
|
||||
expect('.status-highlights-enabled-value.button').toExist();
|
||||
expect('.status-highlights-enabled-value.text').not.toExist();
|
||||
}
|
||||
}
|
||||
|
||||
expectServerHandshake = function() {
|
||||
// POST to update course
|
||||
AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/mock-course', {
|
||||
publish: 'republish',
|
||||
metadata: {
|
||||
highlights_enabled_for_messaging: true
|
||||
}
|
||||
});
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// GET updated course
|
||||
AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/outline/mock-course');
|
||||
AjaxHelpers.respondWithJson(
|
||||
requests, createMockCourseJSON({highlights_enabled_for_messaging: true})
|
||||
);
|
||||
};
|
||||
|
||||
openHighlightsSettings = function() {
|
||||
$('button.status-highlights-enabled-value').click();
|
||||
};
|
||||
|
||||
it('does not display settings when disabled', function() {
|
||||
createCourseWithHighlightsDisabled();
|
||||
expect(highlightsSetting()).not.toExist();
|
||||
});
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// GET updated section
|
||||
AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/outline/mock-section');
|
||||
AjaxHelpers.respondWithJson(requests, createMockSectionJSON({highlights: highlights}));
|
||||
};
|
||||
it('displays settings when enabled', function() {
|
||||
createCourseWithHighlights([]);
|
||||
expect(highlightsSetting()).toExist();
|
||||
});
|
||||
|
||||
expectHighlightsToUpdate = function(originalHighlights, updatedHighlights) {
|
||||
createCourseWithHighlights(originalHighlights);
|
||||
it('displays settings as not enabled for messaging', function() {
|
||||
createCourse();
|
||||
expectHighlightsEnabledToBe(false);
|
||||
});
|
||||
|
||||
openHighlights();
|
||||
setHighlights(updatedHighlights);
|
||||
saveHighlights();
|
||||
it('displays settings as enabled for messaging', function() {
|
||||
createCourse({}, {highlights_enabled_for_messaging: true});
|
||||
expectHighlightsEnabledToBe(true);
|
||||
});
|
||||
|
||||
expectServerHandshakeWithHighlights(updatedHighlights);
|
||||
expectHighlightLinkNumberToBe(updatedHighlights.length);
|
||||
it('changes settings when enabled for messaging', function() {
|
||||
createCourse();
|
||||
openHighlightsSettings();
|
||||
clickSaveOnModal();
|
||||
expectServerHandshake();
|
||||
expectHighlightsEnabledToBe(true);
|
||||
});
|
||||
|
||||
openHighlights();
|
||||
expectHighlightsToBe(updatedHighlights);
|
||||
};
|
||||
|
||||
it('does not display a link when highlights is disabled', function() {
|
||||
createCourseWithHighlightsDisabled();
|
||||
expect(highlightsLink()).toHaveLength(0);
|
||||
it('does not change settings when enabling is cancelled', function() {
|
||||
createCourse();
|
||||
openHighlightsSettings();
|
||||
clickCancelOnModal();
|
||||
expectHighlightsEnabledToBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('displays link when no highlights exist', function() {
|
||||
createCourseWithHighlights([]);
|
||||
expectHighlightLinkNumberToBe(0);
|
||||
});
|
||||
|
||||
it('displays link when highlights exist', function() {
|
||||
var highlights = mockHighlightValues(2);
|
||||
createCourseWithHighlights(highlights);
|
||||
expectHighlightLinkNumberToBe(2);
|
||||
});
|
||||
describe('Section Highlights', function() {
|
||||
var mockHighlightValues, highlightsLink, highlightInputs, openHighlights, saveHighlights,
|
||||
cancelHighlights, setHighlights, expectHighlightLinkNumberToBe, expectHighlightsToBe,
|
||||
expectServerHandshakeWithHighlights, expectHighlightsToUpdate,
|
||||
maxNumHighlights = 5;
|
||||
|
||||
it('can view when no highlights exist', function() {
|
||||
createCourseWithHighlights([]);
|
||||
openHighlights();
|
||||
expectHighlightsToBe([]);
|
||||
});
|
||||
mockHighlightValues = function(numberOfHighlights) {
|
||||
var highlights = [],
|
||||
i;
|
||||
for (i = 0; i < numberOfHighlights; i++) {
|
||||
highlights.push('Highlight' + (i + 1));
|
||||
}
|
||||
return highlights;
|
||||
};
|
||||
|
||||
it('can view existing highlights', function() {
|
||||
var highlights = mockHighlightValues(2);
|
||||
createCourseWithHighlights(highlights);
|
||||
openHighlights();
|
||||
expectHighlightsToBe(highlights);
|
||||
});
|
||||
highlightsLink = function() {
|
||||
return outlinePage.$('.section-status >> .highlights-button');
|
||||
};
|
||||
|
||||
it('can add highlights', function() {
|
||||
expectHighlightsToUpdate(
|
||||
mockHighlightValues(0),
|
||||
mockHighlightValues(1)
|
||||
);
|
||||
});
|
||||
highlightInputs = function() {
|
||||
return $('.highlight-input-text');
|
||||
};
|
||||
|
||||
it('can remove highlights', function() {
|
||||
expectHighlightsToUpdate(
|
||||
mockHighlightValues(5),
|
||||
mockHighlightValues(3)
|
||||
);
|
||||
});
|
||||
openHighlights = function() {
|
||||
highlightsLink().click();
|
||||
};
|
||||
|
||||
it('can edit highlights', function() {
|
||||
var originalHighlights = mockHighlightValues(3),
|
||||
editedHighlights = originalHighlights;
|
||||
editedHighlights[2] = 'A New Value';
|
||||
expectHighlightsToUpdate(originalHighlights, editedHighlights);
|
||||
saveHighlights = function() {
|
||||
clickSaveOnModal();
|
||||
};
|
||||
|
||||
cancelHighlights = function() {
|
||||
clickCancelOnModal();
|
||||
};
|
||||
|
||||
setHighlights = function(highlights) {
|
||||
var i;
|
||||
for (i = 0; i < highlights.length; i++) {
|
||||
$(highlightInputs()[i]).val(highlights[i]);
|
||||
}
|
||||
for (i = highlights.length; i < maxNumHighlights; i++) {
|
||||
$(highlightInputs()[i]).val('');
|
||||
}
|
||||
};
|
||||
|
||||
expectHighlightLinkNumberToBe = function(expectedNumber) {
|
||||
var link = highlightsLink();
|
||||
expect(link).toContainText('Section Highlights');
|
||||
expect(link.find('.number-highlights')).toHaveHtml(expectedNumber);
|
||||
};
|
||||
|
||||
expectHighlightsToBe = function(expectedHighlights) {
|
||||
var highlights = highlightInputs(),
|
||||
i;
|
||||
|
||||
expect(highlights).toHaveLength(maxNumHighlights);
|
||||
|
||||
for (i = 0; i < expectedHighlights.length; i++) {
|
||||
expect(highlights[i]).toHaveValue(expectedHighlights[i]);
|
||||
}
|
||||
for (i = expectedHighlights.length; i < maxNumHighlights; i++) {
|
||||
expect(highlights[i]).toHaveValue('');
|
||||
expect(highlights[i]).toHaveAttr(
|
||||
'placeholder',
|
||||
'A highlight to look forward to this week.'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
expectServerHandshakeWithHighlights = function(highlights) {
|
||||
// POST to update section
|
||||
AjaxHelpers.expectJsonRequest(requests, 'POST', '/xblock/mock-section', {
|
||||
publish: 'republish',
|
||||
metadata: {
|
||||
highlights: highlights
|
||||
}
|
||||
});
|
||||
AjaxHelpers.respondWithJson(requests, {});
|
||||
|
||||
// GET updated section
|
||||
AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/outline/mock-section');
|
||||
AjaxHelpers.respondWithJson(requests, createMockSectionJSON({highlights: highlights}));
|
||||
};
|
||||
|
||||
expectHighlightsToUpdate = function(originalHighlights, updatedHighlights) {
|
||||
createCourseWithHighlights(originalHighlights);
|
||||
|
||||
openHighlights();
|
||||
setHighlights(updatedHighlights);
|
||||
saveHighlights();
|
||||
|
||||
expectServerHandshakeWithHighlights(updatedHighlights);
|
||||
expectHighlightLinkNumberToBe(updatedHighlights.length);
|
||||
|
||||
openHighlights();
|
||||
expectHighlightsToBe(updatedHighlights);
|
||||
};
|
||||
|
||||
it('does not display link when disabled', function() {
|
||||
createCourseWithHighlightsDisabled();
|
||||
expect(highlightsLink()).not.toExist();
|
||||
});
|
||||
|
||||
it('displays link when no highlights exist', function() {
|
||||
createCourseWithHighlights([]);
|
||||
expectHighlightLinkNumberToBe(0);
|
||||
});
|
||||
|
||||
it('displays link when highlights exist', function() {
|
||||
var highlights = mockHighlightValues(2);
|
||||
createCourseWithHighlights(highlights);
|
||||
expectHighlightLinkNumberToBe(2);
|
||||
});
|
||||
|
||||
it('can view when no highlights exist', function() {
|
||||
createCourseWithHighlights([]);
|
||||
openHighlights();
|
||||
expectHighlightsToBe([]);
|
||||
});
|
||||
|
||||
it('can view existing highlights', function() {
|
||||
var highlights = mockHighlightValues(2);
|
||||
createCourseWithHighlights(highlights);
|
||||
openHighlights();
|
||||
expectHighlightsToBe(highlights);
|
||||
});
|
||||
|
||||
it('does not save highlights when cancelled', function() {
|
||||
var originalHighlights = mockHighlightValues(2),
|
||||
editedHighlights = originalHighlights;
|
||||
editedHighlights[1] = 'A New Value';
|
||||
|
||||
createCourseWithHighlights(originalHighlights);
|
||||
openHighlights();
|
||||
setHighlights(editedHighlights);
|
||||
|
||||
cancelHighlights();
|
||||
AjaxHelpers.expectNoRequests(requests);
|
||||
|
||||
openHighlights();
|
||||
expectHighlightsToBe(originalHighlights);
|
||||
});
|
||||
|
||||
it('can add highlights', function() {
|
||||
expectHighlightsToUpdate(
|
||||
mockHighlightValues(0),
|
||||
mockHighlightValues(1)
|
||||
);
|
||||
});
|
||||
|
||||
it('can remove highlights', function() {
|
||||
expectHighlightsToUpdate(
|
||||
mockHighlightValues(5),
|
||||
mockHighlightValues(3)
|
||||
);
|
||||
});
|
||||
|
||||
it('can edit highlights', function() {
|
||||
var originalHighlights = mockHighlightValues(3),
|
||||
editedHighlights = originalHighlights;
|
||||
editedHighlights[2] = 'A New Value';
|
||||
expectHighlightsToUpdate(originalHighlights, editedHighlights);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
54
cms/static/js/views/course_highlights_enable.js
Normal file
54
cms/static/js/views/course_highlights_enable.js
Normal file
@@ -0,0 +1,54 @@
|
||||
define([
|
||||
'jquery', 'underscore', 'backbone', 'js/views/utils/xblock_utils', 'js/utils/templates',
|
||||
'js/views/modals/course_outline_modals', 'edx-ui-toolkit/js/utils/html-utils'],
|
||||
function(
|
||||
$, _, Backbone, XBlockViewUtils, TemplateUtils, CourseOutlineModalsFactory, HtmlUtils
|
||||
) {
|
||||
'use strict';
|
||||
var CourseHighlightsEnableView = Backbone.View.extend({
|
||||
events: {
|
||||
'click button.status-highlights-enabled-value': 'handleEnableButtonPress',
|
||||
'keypress button.status-highlights-enabled-value': 'handleEnableButtonPress'
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
this.template = TemplateUtils.loadTemplate('course-highlights-enable');
|
||||
},
|
||||
|
||||
handleEnableButtonPress: function(event) {
|
||||
if (event.type === 'click' || event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
this.highlightsEnableXBlock();
|
||||
}
|
||||
},
|
||||
|
||||
highlightsEnableXBlock: function() {
|
||||
var modal = CourseOutlineModalsFactory.getModal('highlights_enable', this.model, {
|
||||
onSave: this.refresh.bind(this),
|
||||
xblockType: XBlockViewUtils.getXBlockType(
|
||||
this.model.get('category')
|
||||
)
|
||||
});
|
||||
|
||||
if (modal) {
|
||||
window.analytics.track('edx.bi.highlights_enable.modal_open');
|
||||
modal.show();
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
this.model.fetch({
|
||||
success: this.render.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var html = this.template(this.model.attributes);
|
||||
HtmlUtils.setHtml(this.$el, HtmlUtils.HTML(html));
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
return CourseHighlightsEnableView;
|
||||
}
|
||||
);
|
||||
@@ -17,7 +17,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
AbstractEditor, BaseDateEditor,
|
||||
ReleaseDateEditor, DueDateEditor, GradingEditor, PublishEditor, AbstractVisibilityEditor,
|
||||
StaffLockEditor, UnitAccessEditor, ContentVisibilityEditor, TimedExaminationPreferenceEditor,
|
||||
AccessEditor, ShowCorrectnessEditor, HighlightsEditor;
|
||||
AccessEditor, ShowCorrectnessEditor, HighlightsEditor, HighlightsEnableXBlockModal, HighlightsEnableEditor;
|
||||
|
||||
CourseOutlineXBlockModal = BaseModal.extend({
|
||||
events: _.extend({}, BaseModal.prototype.events, {
|
||||
@@ -242,7 +242,11 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
callAnalytics: function(event) {
|
||||
event.preventDefault();
|
||||
window.analytics.track('edx.bi.highlights.' + event.target.innerText.toLowerCase());
|
||||
this.save(event);
|
||||
if (event.target.className.indexOf('save') !== -1) {
|
||||
this.save(event);
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
addActionButtons: function() {
|
||||
@@ -251,6 +255,44 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
}
|
||||
});
|
||||
|
||||
HighlightsEnableXBlockModal = CourseOutlineXBlockModal.extend({
|
||||
|
||||
events: _.extend({}, CourseOutlineXBlockModal.prototype.events, {
|
||||
'click .action-save': 'callAnalytics',
|
||||
'click .action-cancel': 'callAnalytics'
|
||||
}),
|
||||
|
||||
initialize: function() {
|
||||
CourseOutlineXBlockModal.prototype.initialize.call(this);
|
||||
if (this.options.xblockType) {
|
||||
this.options.modalName = 'highlights-enable-' + this.options.xblockType;
|
||||
}
|
||||
},
|
||||
|
||||
getTitle: function() {
|
||||
return gettext('Enable Weekly Course Highlight Messages');
|
||||
},
|
||||
|
||||
getIntroductionMessage: function() {
|
||||
return '';
|
||||
},
|
||||
|
||||
callAnalytics: function(event) {
|
||||
event.preventDefault();
|
||||
window.analytics.track('edx.bi.highlights_enable.' + event.target.innerText.toLowerCase());
|
||||
if (event.target.className.indexOf('save') !== -1) {
|
||||
this.save(event);
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
addActionButtons: function() {
|
||||
this.addActionButton('save', gettext('Enable'), true);
|
||||
this.addActionButton('cancel', gettext('Not yet'));
|
||||
}
|
||||
});
|
||||
|
||||
AbstractEditor = BaseView.extend({
|
||||
tagName: 'section',
|
||||
templateName: null,
|
||||
@@ -933,6 +975,42 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
}
|
||||
});
|
||||
|
||||
HighlightsEnableEditor = AbstractEditor.extend({
|
||||
templateName: 'highlights-enable-editor',
|
||||
className: 'edit-enable-highlights',
|
||||
|
||||
currentValue: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
hasChanges: function() {
|
||||
return this.model.get('highlights_enabled_for_messaging') !== this.currentValue();
|
||||
},
|
||||
|
||||
getRequestData: function() {
|
||||
if (this.hasChanges()) {
|
||||
return {
|
||||
publish: 'republish',
|
||||
metadata: {
|
||||
highlights_enabled_for_messaging: this.currentValue()
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
getContext: function() {
|
||||
return $.extend(
|
||||
{},
|
||||
AbstractEditor.prototype.getContext.call(this),
|
||||
{
|
||||
highlights_enabled: this.model.get('highlights_enabled_for_messaging'),
|
||||
highlights_doc_url: this.model.get('highlights_doc_url')
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
getModal: function(type, xblockInfo, options) {
|
||||
if (type === 'edit') {
|
||||
@@ -941,6 +1019,8 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
return this.getPublishModal(xblockInfo, options);
|
||||
} else if (type === 'highlights') {
|
||||
return this.getHighlightsModal(xblockInfo, options);
|
||||
} else if (type === 'highlights_enable') {
|
||||
return this.getHighlightsEnableModal(xblockInfo, options);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -1018,6 +1098,13 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
|
||||
editors: [HighlightsEditor],
|
||||
model: xblockInfo
|
||||
}, options));
|
||||
},
|
||||
|
||||
getHighlightsEnableModal: function(xblockInfo, options) {
|
||||
return new HighlightsEnableXBlockModal($.extend({
|
||||
editors: [HighlightsEnableEditor],
|
||||
model: xblockInfo
|
||||
}, options));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
/**
|
||||
* This page is used to show the user an outline of the course.
|
||||
*/
|
||||
define(['jquery', 'underscore', 'gettext', 'js/views/pages/base_page', 'js/views/utils/xblock_utils',
|
||||
'js/views/course_outline', 'common/js/components/utils/view_utils', 'common/js/components/views/feedback_alert',
|
||||
'common/js/components/views/feedback_notification'],
|
||||
function($, _, gettext, BasePage, XBlockViewUtils, CourseOutlineView, ViewUtils, AlertView, NoteView) {
|
||||
define([
|
||||
'jquery', 'underscore', 'gettext', 'js/views/pages/base_page', 'js/views/utils/xblock_utils',
|
||||
'js/views/course_outline', 'common/js/components/utils/view_utils', 'common/js/components/views/feedback_alert',
|
||||
'common/js/components/views/feedback_notification', 'js/views/course_highlights_enable'],
|
||||
function($, _, gettext, BasePage, XBlockViewUtils, CourseOutlineView, ViewUtils, AlertView, NoteView,
|
||||
CourseHighlightsEnableView
|
||||
) {
|
||||
'use strict';
|
||||
var expandedLocators, CourseOutlinePage;
|
||||
|
||||
CourseOutlinePage = BasePage.extend({
|
||||
@@ -65,6 +69,15 @@ define(['jquery', 'underscore', 'gettext', 'js/views/pages/base_page', 'js/views
|
||||
this.expandedLocators.addAll(this.initialState.expanded_locators);
|
||||
}
|
||||
|
||||
/* globals course */
|
||||
if (this.model.get('highlights_enabled') && course.get('self_paced')) {
|
||||
this.highlightsEnableView = new CourseHighlightsEnableView({
|
||||
el: this.$('.status-highlights-enabled'),
|
||||
model: this.model
|
||||
});
|
||||
this.highlightsEnableView.render();
|
||||
}
|
||||
|
||||
this.outlineView = new CourseOutlineView({
|
||||
el: this.$('.outline'),
|
||||
model: this.model,
|
||||
|
||||
@@ -184,11 +184,13 @@
|
||||
margin-bottom: $baseline;
|
||||
|
||||
.status-release,
|
||||
.status-pacing {
|
||||
.status-pacing,
|
||||
.status-highlights-enabled {
|
||||
@extend %t-copy-base;
|
||||
|
||||
display: inline-block;
|
||||
color: $color-copy-base;
|
||||
margin-right: $baseline;
|
||||
|
||||
// STATE: hover
|
||||
&:hover {
|
||||
@@ -198,10 +200,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
.status-highlights-enabled {
|
||||
margin-left: $baseline * 1.6;
|
||||
}
|
||||
|
||||
.status-release-label,
|
||||
.status-release-value,
|
||||
.status-pacing-label,
|
||||
.status-pacing-value,
|
||||
.status-highlights-enabled-label,
|
||||
.status-highlights-enabled-value,
|
||||
.status-highlights-enabled-info,
|
||||
.status-actions {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
@@ -209,13 +218,28 @@
|
||||
}
|
||||
|
||||
.status-release-label,
|
||||
.status-pacing-label {
|
||||
.status-pacing-label,
|
||||
.status-highlights-enabled-label {
|
||||
margin-right: ($baseline/4);
|
||||
}
|
||||
|
||||
.status-release-value,
|
||||
.status-pacing-value {
|
||||
.status-pacing-value,
|
||||
.status-highlights-enabled-value {
|
||||
@extend %t-strong;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.status-highlights-enabled-info {
|
||||
font-size: smaller;
|
||||
margin-left: $baseline / 2;
|
||||
}
|
||||
|
||||
.status-highlights-enabled-value.button {
|
||||
@extend %btn-primary-blue;
|
||||
@extend %sizing;
|
||||
padding: 5px 8px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.status-actions {
|
||||
|
||||
@@ -26,7 +26,7 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
|
||||
<%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', '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']:
|
||||
% for template_name in ['course-outline', 'xblock-string-field-editor', 'basic-modal', 'modal-button', 'course-outline-modal', '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']:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="js/${template_name}.underscore" />
|
||||
</script>
|
||||
@@ -152,7 +152,8 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
<article class="content-primary" role="main">
|
||||
<div class="course-status">
|
||||
<div class="status-release">
|
||||
<h2 class="status-release-label">${_("Course Start Date:")}</h2>
|
||||
<h2 class="status-release-label">${_("Course Start Date")}</h2>
|
||||
<br>
|
||||
<p class="status-release-value">${course_release_date}</p>
|
||||
|
||||
<ul class="status-actions">
|
||||
@@ -166,14 +167,16 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
</div>
|
||||
% if SelfPacedConfiguration.current().enabled:
|
||||
<div class="status-pacing">
|
||||
<h2 class=status-pacing-label>${_("Course Pacing:")}</h2>
|
||||
<h2 class=status-pacing-label>${_("Course Pacing")}</h2>
|
||||
<br>
|
||||
% if context_course.self_paced:
|
||||
<p class="status-pacing-value">${_("Self-Paced")}</p>
|
||||
% else:
|
||||
<p class="status-pacing-value">${_("Instructor-Paced")}</p>
|
||||
% endif
|
||||
</div>
|
||||
% endif
|
||||
% endif
|
||||
<div class="status-highlights-enabled"></div>
|
||||
</div>
|
||||
<div class="wrapper-dnd"
|
||||
% if getattr(context_course, 'language'):
|
||||
|
||||
12
cms/templates/js/course-highlights-enable.underscore
Normal file
12
cms/templates/js/course-highlights-enable.underscore
Normal file
@@ -0,0 +1,12 @@
|
||||
<div class="course-highlights-setting">
|
||||
<h2 id="highlights-enabled-label" class="status-highlights-enabled-label">
|
||||
<%- gettext('Weekly Highlight Emails') %>
|
||||
</h2>
|
||||
<br>
|
||||
<% if (highlights_enabled_for_messaging) { %>
|
||||
<span class="status-highlights-enabled-value text"><%- gettext('Enabled') %></span>
|
||||
<% } else { %>
|
||||
<button class="status-highlights-enabled-value button" aria-labelledby="highlights-enabled-label"><%- gettext('Enable Now') %></button>
|
||||
<% } %>
|
||||
<a class="status-highlights-enabled-info" href="<%- highlights_doc_url %>">Learn more</a>
|
||||
</div>
|
||||
@@ -19,7 +19,10 @@
|
||||
'read our {linkStart}documentation{linkEnd}.'
|
||||
),
|
||||
{
|
||||
linkStart: edx.HtmlUtils.HTML('<a href="' + highlights_doc_url + '">'),
|
||||
linkStart: edx.HtmlUtils.interpolateHtml(
|
||||
edx.HtmlUtils.HTML('<a href={highlightsDocUrl}">'),
|
||||
{highlightsDocUrl: highlights_doc_url}
|
||||
),
|
||||
linkEnd: edx.HtmlUtils.HTML('</a>')
|
||||
}
|
||||
) %>
|
||||
|
||||
24
cms/templates/js/highlights-enable-editor.underscore
Normal file
24
cms/templates/js/highlights-enable-editor.underscore
Normal file
@@ -0,0 +1,24 @@
|
||||
<p>
|
||||
<%- gettext(
|
||||
'When you enable weekly course highlight messages, learners ' +
|
||||
'automatically receive weekly email messages for each section that ' +
|
||||
'has highlights. You cannot disable highlights after you start ' +
|
||||
'sending them.'
|
||||
) %>
|
||||
</p>
|
||||
<p>
|
||||
<% // xss-lint: disable=underscore-not-escaped %>
|
||||
<%= edx.HtmlUtils.interpolateHtml(
|
||||
gettext(
|
||||
'Are you sure you want to enable weekly course highlight messages? '
|
||||
+ '{linkStart}Learn more.{linkEnd}'
|
||||
),
|
||||
{
|
||||
linkStart: edx.HtmlUtils.interpolateHtml(
|
||||
edx.HtmlUtils.HTML('<a href="{highlightsDocUrl}" target="_blank">'),
|
||||
{highlightsDocUrl: xblockInfo.attributes.highlights_doc_url}
|
||||
),
|
||||
linkEnd: edx.HtmlUtils.HTML('</a>')
|
||||
}
|
||||
) %>
|
||||
</p>
|
||||
@@ -37,6 +37,9 @@
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<article class="content-primary" role="main">
|
||||
<div class=course-status"">
|
||||
<div class="status-highlights-enabled"></div>
|
||||
</div>
|
||||
<div class="wrapper-dnd">
|
||||
<article class="outline outline-course" data-locator="mock-course" data-course-key="slashes:MockCourse">
|
||||
<div class="no-content add-xblock-component">
|
||||
|
||||
@@ -5,6 +5,7 @@ Acceptance tests for studio related to the outline page.
|
||||
import itertools
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from unittest import skip
|
||||
|
||||
from nose.plugins.attrib import attr
|
||||
from pytz import UTC
|
||||
@@ -115,6 +116,7 @@ class CourseOutlineDragAndDropTest(CourseOutlineTest):
|
||||
expected_ordering
|
||||
)
|
||||
|
||||
@skip("Fails in Firefox 45 but passes in Chrome")
|
||||
def test_drop_unit_in_collapsed_subsection(self):
|
||||
"""
|
||||
Drag vertical "1.1.2" from subsection "1.1" into collapsed subsection "1.2" which already
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
"""
|
||||
Contains methods for accessing weekly course highlights. Weekly highlights is a
|
||||
schedule experience built on the Schedules app.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from courseware.module_render import get_module_for_descriptor
|
||||
from courseware.model_data import FieldDataCache
|
||||
from openedx.core.djangoapps.schedules.config import COURSE_UPDATE_WAFFLE_FLAG
|
||||
@@ -6,6 +12,8 @@ from request_cache import get_request_or_stub
|
||||
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def course_has_highlights(course_key):
|
||||
"""
|
||||
@@ -13,35 +21,63 @@ def course_has_highlights(course_key):
|
||||
This ignores access checks, since highlights may be lurking in currently
|
||||
inaccessible content.
|
||||
"""
|
||||
if not COURSE_UPDATE_WAFFLE_FLAG.is_enabled(course_key):
|
||||
try:
|
||||
course = _get_course_with_highlights(course_key)
|
||||
|
||||
except CourseUpdateDoesNotExist:
|
||||
return False
|
||||
|
||||
course = modulestore().get_course(course_key, depth=1)
|
||||
return any(
|
||||
section.highlights
|
||||
for section in course.get_children()
|
||||
if not section.hide_from_toc
|
||||
)
|
||||
else:
|
||||
highlights_are_available = any(
|
||||
section.highlights
|
||||
for section in course.get_children()
|
||||
if not section.hide_from_toc
|
||||
)
|
||||
|
||||
if not highlights_are_available:
|
||||
log.error(
|
||||
"Course team enabled highlights and provided no highlights."
|
||||
)
|
||||
|
||||
return highlights_are_available
|
||||
|
||||
|
||||
def get_week_highlights(user, course_key, week_num):
|
||||
"""
|
||||
Get highlights (list of unicode strings) for a given week.
|
||||
week_num starts at 1.
|
||||
Raises CourseUpdateDoesNotExist if highlights do not exist for
|
||||
the requested week_num.
|
||||
|
||||
Raises:
|
||||
CourseUpdateDoesNotExist: if highlights do not exist for
|
||||
the requested week_num.
|
||||
"""
|
||||
course_descriptor = _get_course_with_highlights(course_key)
|
||||
course_module = _get_course_module(course_descriptor, user)
|
||||
sections_with_highlights = _get_sections_with_highlights(course_module)
|
||||
highlights = _get_highlights_for_week(
|
||||
sections_with_highlights,
|
||||
week_num,
|
||||
course_key,
|
||||
)
|
||||
return highlights
|
||||
|
||||
|
||||
def _get_course_with_highlights(course_key):
|
||||
# pylint: disable=missing-docstring
|
||||
if not COURSE_UPDATE_WAFFLE_FLAG.is_enabled(course_key):
|
||||
raise CourseUpdateDoesNotExist(
|
||||
"%s does not have Course Updates enabled.",
|
||||
course_key
|
||||
"%s Course Update Messages waffle flag is disabled.",
|
||||
course_key,
|
||||
)
|
||||
|
||||
course_descriptor = _get_course_descriptor(course_key)
|
||||
course_module = _get_course_module(course_descriptor, user)
|
||||
sections_with_highlights = _get_sections_with_highlights(course_module)
|
||||
highlights = _get_highlights_for_week(sections_with_highlights, week_num, course_key)
|
||||
return highlights
|
||||
if not course_descriptor.highlights_enabled_for_messaging:
|
||||
raise CourseUpdateDoesNotExist(
|
||||
"%s Course Update Messages are disabled.",
|
||||
course_key,
|
||||
)
|
||||
|
||||
return course_descriptor
|
||||
|
||||
|
||||
def _get_course_descriptor(course_key):
|
||||
|
||||
@@ -21,7 +21,9 @@ class TestContentHighlights(ModuleStoreTestCase):
|
||||
self._setup_user()
|
||||
|
||||
def _setup_course(self):
|
||||
self.course = CourseFactory.create()
|
||||
self.course = CourseFactory.create(
|
||||
highlights_enabled_for_messaging=True
|
||||
)
|
||||
self.course_key = self.course.id
|
||||
|
||||
def _setup_user(self):
|
||||
@@ -66,6 +68,23 @@ class TestContentHighlights(ModuleStoreTestCase):
|
||||
highlights,
|
||||
)
|
||||
|
||||
@override_waffle_flag(COURSE_UPDATE_WAFFLE_FLAG, True)
|
||||
def test_highlights_disabled_for_messaging(self):
|
||||
highlights = [u'A test highlight.']
|
||||
with self.store.bulk_operations(self.course_key):
|
||||
self._create_chapter(highlights=highlights)
|
||||
self.course.highlights_enabled_for_messaging = False
|
||||
self.store.update_item(self.course, self.user.id)
|
||||
|
||||
self.assertFalse(course_has_highlights(self.course_key))
|
||||
|
||||
with self.assertRaises(CourseUpdateDoesNotExist):
|
||||
get_week_highlights(
|
||||
self.user,
|
||||
self.course_key,
|
||||
week_num=1,
|
||||
)
|
||||
|
||||
@override_waffle_flag(COURSE_UPDATE_WAFFLE_FLAG, True)
|
||||
def test_course_with_no_highlights(self):
|
||||
with self.store.bulk_operations(self.course_key):
|
||||
|
||||
@@ -47,7 +47,7 @@ edx-lint==0.4.3
|
||||
astroid==1.3.8
|
||||
edx-django-oauth2-provider==1.2.5
|
||||
edx-django-sites-extensions==2.3.0
|
||||
edx-enterprise==0.53.16
|
||||
edx-enterprise==0.53.18
|
||||
edx-oauth2-provider==1.2.2
|
||||
edx-opaque-keys==0.4.0
|
||||
edx-organizations==0.4.8
|
||||
@@ -204,3 +204,6 @@ py2neo==3.1.2
|
||||
# Support for plugins
|
||||
web-fragments==0.2.2
|
||||
xblock==1.0.0
|
||||
|
||||
# Redis version
|
||||
redis==2.10.6
|
||||
|
||||
Reference in New Issue
Block a user