Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
c21895a8
Unverified
Commit
c21895a8
authored
Nov 28, 2017
by
Alessandro Roux
Committed by
GitHub
Nov 28, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #16646 from edx/pacing/highlights-setting-UI-for-courses
Pacing/highlights setting ui for courses
parents
7f60dcb2
9f74e705
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
445 additions
and
42 deletions
+445
-42
cms/djangoapps/contentstore/views/item.py
+8
-2
cms/djangoapps/contentstore/views/tests/test_item.py
+5
-3
cms/static/js/models/xblock_info.js
+1
-0
cms/static/js/spec/views/pages/course_outline_spec.js
+129
-15
cms/static/js/views/course_highlights_enable.js
+54
-0
cms/static/js/views/modals/course_outline_modals.js
+88
-1
cms/static/js/views/pages/course_outline.js
+16
-3
cms/static/sass/views/_outline.scss
+27
-3
cms/templates/course_outline.html
+6
-3
cms/templates/js/course-highlights-enable.underscore
+12
-0
cms/templates/js/highlights-editor.underscore
+4
-1
cms/templates/js/highlights-enable-editor.underscore
+24
-0
cms/templates/js/mock/mock-course-outline-page.underscore
+3
-0
common/test/acceptance/tests/studio/test_studio_outline.py
+2
-0
openedx/core/djangoapps/schedules/content_highlights.py
+46
-10
openedx/core/djangoapps/schedules/tests/test_content_highlights.py
+20
-1
No files found.
cms/djangoapps/contentstore/views/item.py
View file @
c21895a8
...
@@ -1188,11 +1188,17 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
...
@@ -1188,11 +1188,17 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
xblock_info
.
update
({
xblock_info
.
update
({
'hide_after_due'
:
xblock
.
hide_after_due
,
'hide_after_due'
:
xblock
.
hide_after_due
,
})
})
elif
xblock
.
category
==
'chapter'
:
elif
xblock
.
category
in
(
'chapter'
,
'course'
):
if
xblock
.
category
==
'chapter'
:
xblock_info
.
update
({
xblock_info
.
update
({
'highlights'
:
xblock
.
highlights
,
'highlights'
:
xblock
.
highlights
,
'highlights_enabled'
:
highlights_setting
.
is_enabled
(),
})
elif
xblock
.
category
==
'course'
:
xblock_info
.
update
({
'highlights_enabled_for_messaging'
:
course
.
highlights_enabled_for_messaging
,
'highlights_enabled_for_messaging'
:
course
.
highlights_enabled_for_messaging
,
})
xblock_info
.
update
({
'highlights_enabled'
:
highlights_setting
.
is_enabled
(),
'highlights_preview_only'
:
not
COURSE_UPDATE_WAFFLE_FLAG
.
is_enabled
(
course
.
id
),
'highlights_preview_only'
:
not
COURSE_UPDATE_WAFFLE_FLAG
.
is_enabled
(
course
.
id
),
'highlights_doc_url'
:
HelpUrlExpert
.
the_one
()
.
url_for_token
(
'content_highlights'
),
'highlights_doc_url'
:
HelpUrlExpert
.
the_one
()
.
url_for_token
(
'content_highlights'
),
})
})
...
...
cms/djangoapps/contentstore/views/tests/test_item.py
View file @
c21895a8
...
@@ -2577,8 +2577,10 @@ class TestXBlockInfo(ItemTest):
...
@@ -2577,8 +2577,10 @@ class TestXBlockInfo(ItemTest):
self
.
store
.
update_item
(
self
.
course
,
None
)
self
.
store
.
update_item
(
self
.
course
,
None
)
chapter
=
self
.
store
.
get_item
(
self
.
chapter
.
location
)
chapter
=
self
.
store
.
get_item
(
self
.
chapter
.
location
)
with
highlights_setting
.
override
():
with
highlights_setting
.
override
():
xblock_info
=
create_xblock_info
(
chapter
)
chapter_xblock_info
=
create_xblock_info
(
chapter
)
self
.
assertTrue
(
xblock_info
[
'highlights_enabled'
])
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
):
def
validate_course_xblock_info
(
self
,
xblock_info
,
has_child_info
=
True
,
course_outline
=
False
):
"""
"""
...
@@ -2588,6 +2590,7 @@ class TestXBlockInfo(ItemTest):
...
@@ -2588,6 +2590,7 @@ class TestXBlockInfo(ItemTest):
self
.
assertEqual
(
xblock_info
[
'id'
],
unicode
(
self
.
course
.
location
))
self
.
assertEqual
(
xblock_info
[
'id'
],
unicode
(
self
.
course
.
location
))
self
.
assertEqual
(
xblock_info
[
'display_name'
],
self
.
course
.
display_name
)
self
.
assertEqual
(
xblock_info
[
'display_name'
],
self
.
course
.
display_name
)
self
.
assertTrue
(
xblock_info
[
'published'
])
self
.
assertTrue
(
xblock_info
[
'published'
])
self
.
assertFalse
(
xblock_info
[
'highlights_enabled_for_messaging'
])
# Finally, validate the entire response for consistency
# Finally, validate the entire response for consistency
self
.
validate_xblock_info_consistency
(
xblock_info
,
has_child_info
=
has_child_info
,
course_outline
=
course_outline
)
self
.
validate_xblock_info_consistency
(
xblock_info
,
has_child_info
=
has_child_info
,
course_outline
=
course_outline
)
...
@@ -2608,7 +2611,6 @@ class TestXBlockInfo(ItemTest):
...
@@ -2608,7 +2611,6 @@ class TestXBlockInfo(ItemTest):
self
.
assertEqual
(
xblock_info
[
'format'
],
None
)
self
.
assertEqual
(
xblock_info
[
'format'
],
None
)
self
.
assertEqual
(
xblock_info
[
'highlights'
],
self
.
chapter
.
highlights
)
self
.
assertEqual
(
xblock_info
[
'highlights'
],
self
.
chapter
.
highlights
)
self
.
assertFalse
(
xblock_info
[
'highlights_enabled'
])
self
.
assertFalse
(
xblock_info
[
'highlights_enabled'
])
self
.
assertFalse
(
xblock_info
[
'highlights_enabled_for_messaging'
])
# Finally, validate the entire response for consistency
# Finally, validate the entire response for consistency
self
.
validate_xblock_info_consistency
(
xblock_info
,
has_child_info
=
has_child_info
)
self
.
validate_xblock_info_consistency
(
xblock_info
,
has_child_info
=
has_child_info
)
...
...
cms/static/js/models/xblock_info.js
View file @
c21895a8
...
@@ -165,6 +165,7 @@ function(Backbone, _, str, ModuleUtils) {
...
@@ -165,6 +165,7 @@ function(Backbone, _, str, ModuleUtils) {
*/
*/
highlights
:
[],
highlights
:
[],
highlights_enabled
:
false
,
highlights_enabled
:
false
,
highlights_enabled_for_messaging
:
false
,
highlights_preview_only
:
true
,
highlights_preview_only
:
true
,
highlights_doc_url
:
''
highlights_doc_url
:
''
},
},
...
...
cms/static/js/spec/views/pages/course_outline_spec.js
View file @
c21895a8
...
@@ -32,7 +32,9 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
...
@@ -32,7 +32,9 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
children
:
[]
children
:
[]
},
},
user_partitions
:
[],
user_partitions
:
[],
user_partition_info
:
{}
user_partition_info
:
{},
highlights_enabled
:
true
,
highlights_enabled_for_messaging
:
false
},
options
,
{
child_info
:
{
children
:
children
}});
},
options
,
{
child_info
:
{
children
:
children
}});
};
};
...
@@ -262,7 +264,8 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
...
@@ -262,7 +264,8 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
'due-date-editor'
,
'grading-editor'
,
'publish-editor'
,
'due-date-editor'
,
'grading-editor'
,
'publish-editor'
,
'staff-lock-editor'
,
'unit-access-editor'
,
'content-visibility-editor'
,
'staff-lock-editor'
,
'unit-access-editor'
,
'content-visibility-editor'
,
'settings-modal-tabs'
,
'timed-examination-preference-editor'
,
'access-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
);
appendSetFixtures
(
mockOutlinePage
);
mockCourseJSON
=
createMockCourseJSON
({},
[
mockCourseJSON
=
createMockCourseJSON
({},
[
...
@@ -529,20 +532,17 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
...
@@ -529,20 +532,17 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
});
});
});
});
describe
(
'Section Highlights'
,
function
()
{
describe
(
'Content Highlights'
,
function
()
{
var
createCourse
,
createCourseWithHighlights
,
createCourseWithHighlightsDisabled
,
mockHighlightValues
,
var
createCourse
,
createCourseWithHighlights
,
createCourseWithHighlightsDisabled
,
highlightsLink
,
highlightInputs
,
openHighlights
,
saveHighlights
,
setHighlights
,
clickSaveOnModal
,
clickCancelOnModal
;
expectHighlightLinkNumberToBe
,
expectHighlightsToBe
,
expectServerHandshakeWithHighlights
,
expectHighlightsToUpdate
,
maxNumHighlights
=
5
;
beforeEach
(
function
()
{
beforeEach
(
function
()
{
setSelfPaced
();
setSelfPaced
();
});
});
createCourse
=
function
(
sectionOptions
)
{
createCourse
=
function
(
sectionOptions
,
courseOptions
)
{
createCourseOutlinePage
(
this
,
createCourseOutlinePage
(
this
,
createMockCourseJSON
(
{}
,
[
createMockCourseJSON
(
courseOptions
,
[
createMockSectionJSON
(
sectionOptions
)
createMockSectionJSON
(
sectionOptions
)
])
])
);
);
...
@@ -553,9 +553,99 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
...
@@ -553,9 +553,99 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
};
};
createCourseWithHighlightsDisabled
=
function
()
{
createCourseWithHighlightsDisabled
=
function
()
{
createCourse
({
highlights_enabled
:
false
});
var
highlightsDisabled
=
{
highlights_enabled
:
false
};
createCourse
(
highlightsDisabled
,
highlightsDisabled
);
};
clickSaveOnModal
=
function
()
{
$
(
'.wrapper-modal-window .action-save'
).
click
();
};
clickCancelOnModal
=
function
()
{
$
(
'.wrapper-modal-window .action-cancel'
).
click
();
};
describe
(
'Course Highlights Setting'
,
function
()
{
var
highlightsSetting
,
expectHighlightsEnabledToBe
,
expectServerHandshake
,
openHighlightsSettings
;
highlightsSetting
=
function
()
{
return
$
(
'.course-highlights-setting'
);
};
};
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
();
});
it
(
'displays settings when enabled'
,
function
()
{
createCourseWithHighlights
([]);
expect
(
highlightsSetting
()).
toExist
();
});
it
(
'displays settings as not enabled for messaging'
,
function
()
{
createCourse
();
expectHighlightsEnabledToBe
(
false
);
});
it
(
'displays settings as enabled for messaging'
,
function
()
{
createCourse
({},
{
highlights_enabled_for_messaging
:
true
});
expectHighlightsEnabledToBe
(
true
);
});
it
(
'changes settings when enabled for messaging'
,
function
()
{
createCourse
();
openHighlightsSettings
();
clickSaveOnModal
();
expectServerHandshake
();
expectHighlightsEnabledToBe
(
true
);
});
it
(
'does not change settings when enabling is cancelled'
,
function
()
{
createCourse
();
openHighlightsSettings
();
clickCancelOnModal
();
expectHighlightsEnabledToBe
(
false
);
});
});
describe
(
'Section Highlights'
,
function
()
{
var
mockHighlightValues
,
highlightsLink
,
highlightInputs
,
openHighlights
,
saveHighlights
,
cancelHighlights
,
setHighlights
,
expectHighlightLinkNumberToBe
,
expectHighlightsToBe
,
expectServerHandshakeWithHighlights
,
expectHighlightsToUpdate
,
maxNumHighlights
=
5
;
mockHighlightValues
=
function
(
numberOfHighlights
)
{
mockHighlightValues
=
function
(
numberOfHighlights
)
{
var
highlights
=
[],
var
highlights
=
[],
i
;
i
;
...
@@ -578,7 +668,11 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
...
@@ -578,7 +668,11 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
};
};
saveHighlights
=
function
()
{
saveHighlights
=
function
()
{
$
(
'.wrapper-modal-window .action-save'
).
click
();
clickSaveOnModal
();
};
cancelHighlights
=
function
()
{
clickCancelOnModal
();
};
};
setHighlights
=
function
(
highlights
)
{
setHighlights
=
function
(
highlights
)
{
...
@@ -608,7 +702,10 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
...
@@ -608,7 +702,10 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
}
}
for
(
i
=
expectedHighlights
.
length
;
i
<
maxNumHighlights
;
i
++
)
{
for
(
i
=
expectedHighlights
.
length
;
i
<
maxNumHighlights
;
i
++
)
{
expect
(
highlights
[
i
]).
toHaveValue
(
''
);
expect
(
highlights
[
i
]).
toHaveValue
(
''
);
expect
(
highlights
[
i
]).
toHaveAttr
(
'placeholder'
,
'A highlight to look forward to this week.'
);
expect
(
highlights
[
i
]).
toHaveAttr
(
'placeholder'
,
'A highlight to look forward to this week.'
);
}
}
};
};
...
@@ -641,9 +738,9 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
...
@@ -641,9 +738,9 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
expectHighlightsToBe
(
updatedHighlights
);
expectHighlightsToBe
(
updatedHighlights
);
};
};
it
(
'does not display a link when highlights is
disabled'
,
function
()
{
it
(
'does not display link when
disabled'
,
function
()
{
createCourseWithHighlightsDisabled
();
createCourseWithHighlightsDisabled
();
expect
(
highlightsLink
()).
toHaveLength
(
0
);
expect
(
highlightsLink
()).
not
.
toExist
(
);
});
});
it
(
'displays link when no highlights exist'
,
function
()
{
it
(
'displays link when no highlights exist'
,
function
()
{
...
@@ -670,6 +767,22 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
...
@@ -670,6 +767,22 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
expectHighlightsToBe
(
highlights
);
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
()
{
it
(
'can add highlights'
,
function
()
{
expectHighlightsToUpdate
(
expectHighlightsToUpdate
(
mockHighlightValues
(
0
),
mockHighlightValues
(
0
),
...
@@ -691,6 +804,7 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
...
@@ -691,6 +804,7 @@ define(['jquery', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'common/j
expectHighlightsToUpdate
(
originalHighlights
,
editedHighlights
);
expectHighlightsToUpdate
(
originalHighlights
,
editedHighlights
);
});
});
});
});
});
describe
(
'Section'
,
function
()
{
describe
(
'Section'
,
function
()
{
var
getDisplayNameWrapper
;
var
getDisplayNameWrapper
;
...
...
cms/static/js/views/course_highlights_enable.js
0 → 100644
View file @
c21895a8
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
;
}
);
cms/static/js/views/modals/course_outline_modals.js
View file @
c21895a8
...
@@ -17,7 +17,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
...
@@ -17,7 +17,7 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
AbstractEditor
,
BaseDateEditor
,
AbstractEditor
,
BaseDateEditor
,
ReleaseDateEditor
,
DueDateEditor
,
GradingEditor
,
PublishEditor
,
AbstractVisibilityEditor
,
ReleaseDateEditor
,
DueDateEditor
,
GradingEditor
,
PublishEditor
,
AbstractVisibilityEditor
,
StaffLockEditor
,
UnitAccessEditor
,
ContentVisibilityEditor
,
TimedExaminationPreferenceEditor
,
StaffLockEditor
,
UnitAccessEditor
,
ContentVisibilityEditor
,
TimedExaminationPreferenceEditor
,
AccessEditor
,
ShowCorrectnessEditor
,
HighlightsEditor
;
AccessEditor
,
ShowCorrectnessEditor
,
HighlightsEditor
,
HighlightsEnableXBlockModal
,
HighlightsEnableEditor
;
CourseOutlineXBlockModal
=
BaseModal
.
extend
({
CourseOutlineXBlockModal
=
BaseModal
.
extend
({
events
:
_
.
extend
({},
BaseModal
.
prototype
.
events
,
{
events
:
_
.
extend
({},
BaseModal
.
prototype
.
events
,
{
...
@@ -242,7 +242,11 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
...
@@ -242,7 +242,11 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
callAnalytics
:
function
(
event
)
{
callAnalytics
:
function
(
event
)
{
event
.
preventDefault
();
event
.
preventDefault
();
window
.
analytics
.
track
(
'edx.bi.highlights.'
+
event
.
target
.
innerText
.
toLowerCase
());
window
.
analytics
.
track
(
'edx.bi.highlights.'
+
event
.
target
.
innerText
.
toLowerCase
());
if
(
event
.
target
.
className
.
indexOf
(
'save'
)
!==
-
1
)
{
this
.
save
(
event
);
this
.
save
(
event
);
}
else
{
this
.
hide
();
}
},
},
addActionButtons
:
function
()
{
addActionButtons
:
function
()
{
...
@@ -251,6 +255,44 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
...
@@ -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
({
AbstractEditor
=
BaseView
.
extend
({
tagName
:
'section'
,
tagName
:
'section'
,
templateName
:
null
,
templateName
:
null
,
...
@@ -933,6 +975,42 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
...
@@ -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
{
return
{
getModal
:
function
(
type
,
xblockInfo
,
options
)
{
getModal
:
function
(
type
,
xblockInfo
,
options
)
{
if
(
type
===
'edit'
)
{
if
(
type
===
'edit'
)
{
...
@@ -941,6 +1019,8 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
...
@@ -941,6 +1019,8 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
return
this
.
getPublishModal
(
xblockInfo
,
options
);
return
this
.
getPublishModal
(
xblockInfo
,
options
);
}
else
if
(
type
===
'highlights'
)
{
}
else
if
(
type
===
'highlights'
)
{
return
this
.
getHighlightsModal
(
xblockInfo
,
options
);
return
this
.
getHighlightsModal
(
xblockInfo
,
options
);
}
else
if
(
type
===
'highlights_enable'
)
{
return
this
.
getHighlightsEnableModal
(
xblockInfo
,
options
);
}
else
{
}
else
{
return
null
;
return
null
;
}
}
...
@@ -1018,6 +1098,13 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
...
@@ -1018,6 +1098,13 @@ define(['jquery', 'backbone', 'underscore', 'gettext', 'js/views/baseview',
editors
:
[
HighlightsEditor
],
editors
:
[
HighlightsEditor
],
model
:
xblockInfo
model
:
xblockInfo
},
options
));
},
options
));
},
getHighlightsEnableModal
:
function
(
xblockInfo
,
options
)
{
return
new
HighlightsEnableXBlockModal
(
$
.
extend
({
editors
:
[
HighlightsEnableEditor
],
model
:
xblockInfo
},
options
));
}
}
};
};
});
});
cms/static/js/views/pages/course_outline.js
View file @
c21895a8
/**
/**
* This page is used to show the user an outline of the course.
* 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'
,
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'
,
'js/views/course_outline'
,
'common/js/components/utils/view_utils'
,
'common/js/components/views/feedback_alert'
,
'common/js/components/views/feedback_notification'
],
'common/js/components/views/feedback_notification'
,
'js/views/course_highlights_enable'
],
function
(
$
,
_
,
gettext
,
BasePage
,
XBlockViewUtils
,
CourseOutlineView
,
ViewUtils
,
AlertView
,
NoteView
)
{
function
(
$
,
_
,
gettext
,
BasePage
,
XBlockViewUtils
,
CourseOutlineView
,
ViewUtils
,
AlertView
,
NoteView
,
CourseHighlightsEnableView
)
{
'use strict'
;
var
expandedLocators
,
CourseOutlinePage
;
var
expandedLocators
,
CourseOutlinePage
;
CourseOutlinePage
=
BasePage
.
extend
({
CourseOutlinePage
=
BasePage
.
extend
({
...
@@ -65,6 +69,15 @@ define(['jquery', 'underscore', 'gettext', 'js/views/pages/base_page', 'js/views
...
@@ -65,6 +69,15 @@ define(['jquery', 'underscore', 'gettext', 'js/views/pages/base_page', 'js/views
this
.
expandedLocators
.
addAll
(
this
.
initialState
.
expanded_locators
);
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
({
this
.
outlineView
=
new
CourseOutlineView
({
el
:
this
.
$
(
'.outline'
),
el
:
this
.
$
(
'.outline'
),
model
:
this
.
model
,
model
:
this
.
model
,
...
...
cms/static/sass/views/_outline.scss
View file @
c21895a8
...
@@ -184,11 +184,13 @@
...
@@ -184,11 +184,13 @@
margin-bottom
:
$baseline
;
margin-bottom
:
$baseline
;
.status-release
,
.status-release
,
.status-pacing
{
.status-pacing
,
.status-highlights-enabled
{
@extend
%t-copy-base
;
@extend
%t-copy-base
;
display
:
inline-block
;
display
:
inline-block
;
color
:
$color-copy-base
;
color
:
$color-copy-base
;
margin-right
:
$baseline
;
// STATE: hover
// STATE: hover
&
:hover
{
&
:hover
{
...
@@ -198,10 +200,17 @@
...
@@ -198,10 +200,17 @@
}
}
}
}
.status-highlights-enabled
{
margin-left
:
$baseline
*
1
.6
;
}
.status-release-label
,
.status-release-label
,
.status-release-value
,
.status-release-value
,
.status-pacing-label
,
.status-pacing-label
,
.status-pacing-value
,
.status-pacing-value
,
.status-highlights-enabled-label
,
.status-highlights-enabled-value
,
.status-highlights-enabled-info
,
.status-actions
{
.status-actions
{
display
:
inline-block
;
display
:
inline-block
;
vertical-align
:
middle
;
vertical-align
:
middle
;
...
@@ -209,13 +218,28 @@
...
@@ -209,13 +218,28 @@
}
}
.status-release-label
,
.status-release-label
,
.status-pacing-label
{
.status-pacing-label
,
.status-highlights-enabled-label
{
margin-right
:
(
$baseline
/
4
);
margin-right
:
(
$baseline
/
4
);
}
}
.status-release-value
,
.status-release-value
,
.status-pacing-value
{
.status-pacing-value
,
.status-highlights-enabled-value
{
@extend
%t-strong
;
@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
{
.status-actions
{
...
...
cms/templates/course_outline.html
View file @
c21895a8
...
@@ -26,7 +26,7 @@ from openedx.core.djangolib.markup import HTML, Text
...
@@ -26,7 +26,7 @@ from openedx.core.djangolib.markup import HTML, Text
<
%
block
name=
"header_extras"
>
<
%
block
name=
"header_extras"
>
<link
rel=
"stylesheet"
type=
"text/css"
href=
"${static.url('js/vendor/timepicker/jquery.timepicker.css')}"
/>
<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"
>
<script
type=
"text/template"
id=
"${template_name}-tpl"
>
<%
static
:
include
path
=
"js/${template_name}.underscore"
/>
<%
static
:
include
path
=
"js/${template_name}.underscore"
/>
</script>
</script>
...
@@ -152,7 +152,8 @@ from openedx.core.djangolib.markup import HTML, Text
...
@@ -152,7 +152,8 @@ from openedx.core.djangolib.markup import HTML, Text
<article
class=
"content-primary"
role=
"main"
>
<article
class=
"content-primary"
role=
"main"
>
<div
class=
"course-status"
>
<div
class=
"course-status"
>
<div
class=
"status-release"
>
<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>
<p
class=
"status-release-value"
>
${course_release_date}
</p>
<ul
class=
"status-actions"
>
<ul
class=
"status-actions"
>
...
@@ -166,7 +167,8 @@ from openedx.core.djangolib.markup import HTML, Text
...
@@ -166,7 +167,8 @@ from openedx.core.djangolib.markup import HTML, Text
</div>
</div>
% if SelfPacedConfiguration.current().enabled:
% if SelfPacedConfiguration.current().enabled:
<div
class=
"status-pacing"
>
<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:
% if context_course.self_paced:
<p
class=
"status-pacing-value"
>
${_("Self-Paced")}
</p>
<p
class=
"status-pacing-value"
>
${_("Self-Paced")}
</p>
% else:
% else:
...
@@ -174,6 +176,7 @@ from openedx.core.djangolib.markup import HTML, Text
...
@@ -174,6 +176,7 @@ from openedx.core.djangolib.markup import HTML, Text
% endif
% endif
</div>
</div>
% endif
% endif
<div
class=
"status-highlights-enabled"
></div>
</div>
</div>
<div
class=
"wrapper-dnd"
<div
class=
"wrapper-dnd"
%
if
getattr
(
context_course
,
'
language
')
:
%
if
getattr
(
context_course
,
'
language
')
:
...
...
cms/templates/js/course-highlights-enable.underscore
0 → 100644
View file @
c21895a8
<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>
cms/templates/js/highlights-editor.underscore
View file @
c21895a8
...
@@ -19,7 +19,10 @@
...
@@ -19,7 +19,10 @@
'read our {linkStart}documentation{linkEnd}.'
'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>')
linkEnd: edx.HtmlUtils.HTML('</a>')
}
}
) %>
) %>
...
...
cms/templates/js/highlights-enable-editor.underscore
0 → 100644
View file @
c21895a8
<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>
cms/templates/js/mock/mock-course-outline-page.underscore
View file @
c21895a8
...
@@ -37,6 +37,9 @@
...
@@ -37,6 +37,9 @@
<div class="wrapper-content wrapper">
<div class="wrapper-content wrapper">
<section class="content">
<section class="content">
<article class="content-primary" role="main">
<article class="content-primary" role="main">
<div class=course-status"">
<div class="status-highlights-enabled"></div>
</div>
<div class="wrapper-dnd">
<div class="wrapper-dnd">
<article class="outline outline-course" data-locator="mock-course" data-course-key="slashes:MockCourse">
<article class="outline outline-course" data-locator="mock-course" data-course-key="slashes:MockCourse">
<div class="no-content add-xblock-component">
<div class="no-content add-xblock-component">
...
...
common/test/acceptance/tests/studio/test_studio_outline.py
View file @
c21895a8
...
@@ -5,6 +5,7 @@ Acceptance tests for studio related to the outline page.
...
@@ -5,6 +5,7 @@ Acceptance tests for studio related to the outline page.
import
itertools
import
itertools
import
json
import
json
from
datetime
import
datetime
,
timedelta
from
datetime
import
datetime
,
timedelta
from
unittest
import
skip
from
nose.plugins.attrib
import
attr
from
nose.plugins.attrib
import
attr
from
pytz
import
UTC
from
pytz
import
UTC
...
@@ -115,6 +116,7 @@ class CourseOutlineDragAndDropTest(CourseOutlineTest):
...
@@ -115,6 +116,7 @@ class CourseOutlineDragAndDropTest(CourseOutlineTest):
expected_ordering
expected_ordering
)
)
@skip
(
"Fails in Firefox 45 but passes in Chrome"
)
def
test_drop_unit_in_collapsed_subsection
(
self
):
def
test_drop_unit_in_collapsed_subsection
(
self
):
"""
"""
Drag vertical "1.1.2" from subsection "1.1" into collapsed subsection "1.2" which already
Drag vertical "1.1.2" from subsection "1.1" into collapsed subsection "1.2" which already
...
...
openedx/core/djangoapps/schedules/content_highlights.py
View file @
c21895a8
"""
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.module_render
import
get_module_for_descriptor
from
courseware.model_data
import
FieldDataCache
from
courseware.model_data
import
FieldDataCache
from
openedx.core.djangoapps.schedules.config
import
COURSE_UPDATE_WAFFLE_FLAG
from
openedx.core.djangoapps.schedules.config
import
COURSE_UPDATE_WAFFLE_FLAG
...
@@ -6,6 +12,8 @@ from request_cache import get_request_or_stub
...
@@ -6,6 +12,8 @@ from request_cache import get_request_or_stub
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
log
=
logging
.
getLogger
(
__name__
)
def
course_has_highlights
(
course_key
):
def
course_has_highlights
(
course_key
):
"""
"""
...
@@ -13,35 +21,63 @@ 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
This ignores access checks, since highlights may be lurking in currently
inaccessible content.
inaccessible content.
"""
"""
if
not
COURSE_UPDATE_WAFFLE_FLAG
.
is_enabled
(
course_key
):
try
:
course
=
_get_course_with_highlights
(
course_key
)
except
CourseUpdateDoesNotExist
:
return
False
return
False
course
=
modulestore
()
.
get_course
(
course_key
,
depth
=
1
)
else
:
return
any
(
highlights_are_available
=
any
(
section
.
highlights
section
.
highlights
for
section
in
course
.
get_children
()
for
section
in
course
.
get_children
()
if
not
section
.
hide_from_toc
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
):
def
get_week_highlights
(
user
,
course_key
,
week_num
):
"""
"""
Get highlights (list of unicode strings) for a given week.
Get highlights (list of unicode strings) for a given week.
week_num starts at 1.
week_num starts at 1.
Raises CourseUpdateDoesNotExist if highlights do not exist for
Raises:
CourseUpdateDoesNotExist: if highlights do not exist for
the requested week_num.
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
):
if
not
COURSE_UPDATE_WAFFLE_FLAG
.
is_enabled
(
course_key
):
raise
CourseUpdateDoesNotExist
(
raise
CourseUpdateDoesNotExist
(
"
%
s
does not have Course Updates en
abled."
,
"
%
s
Course Update Messages waffle flag is dis
abled."
,
course_key
course_key
,
)
)
course_descriptor
=
_get_course_descriptor
(
course_key
)
course_descriptor
=
_get_course_descriptor
(
course_key
)
course_module
=
_get_course_module
(
course_descriptor
,
user
)
if
not
course_descriptor
.
highlights_enabled_for_messaging
:
sections_with_highlights
=
_get_sections_with_highlights
(
course_module
)
raise
CourseUpdateDoesNotExist
(
highlights
=
_get_highlights_for_week
(
sections_with_highlights
,
week_num
,
course_key
)
"
%
s Course Update Messages are disabled."
,
return
highlights
course_key
,
)
return
course_descriptor
def
_get_course_descriptor
(
course_key
):
def
_get_course_descriptor
(
course_key
):
...
...
openedx/core/djangoapps/schedules/tests/test_content_highlights.py
View file @
c21895a8
...
@@ -21,7 +21,9 @@ class TestContentHighlights(ModuleStoreTestCase):
...
@@ -21,7 +21,9 @@ class TestContentHighlights(ModuleStoreTestCase):
self
.
_setup_user
()
self
.
_setup_user
()
def
_setup_course
(
self
):
def
_setup_course
(
self
):
self
.
course
=
CourseFactory
.
create
()
self
.
course
=
CourseFactory
.
create
(
highlights_enabled_for_messaging
=
True
)
self
.
course_key
=
self
.
course
.
id
self
.
course_key
=
self
.
course
.
id
def
_setup_user
(
self
):
def
_setup_user
(
self
):
...
@@ -67,6 +69,23 @@ class TestContentHighlights(ModuleStoreTestCase):
...
@@ -67,6 +69,23 @@ class TestContentHighlights(ModuleStoreTestCase):
)
)
@override_waffle_flag
(
COURSE_UPDATE_WAFFLE_FLAG
,
True
)
@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
):
def
test_course_with_no_highlights
(
self
):
with
self
.
store
.
bulk_operations
(
self
.
course_key
):
with
self
.
store
.
bulk_operations
(
self
.
course_key
):
self
.
_create_chapter
(
display_name
=
u"Week 1"
)
self
.
_create_chapter
(
display_name
=
u"Week 1"
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment