diff --git a/lms/static/sass/_build-lms-v1.scss b/lms/static/sass/_build-lms-v1.scss index fae155c3795ab78d5fe99484513528e26476666d..566228e9860864fe955d02c396a3c49cb903f9aa 100644 --- a/lms/static/sass/_build-lms-v1.scss +++ b/lms/static/sass/_build-lms-v1.scss @@ -75,6 +75,7 @@ @import 'features/course-duration-limits'; @import 'features/enterprise-learner-portal-banner'; @import 'features/first-purchase-banner'; +@import 'features/next-up-banner'; // search @import 'search/search'; diff --git a/lms/static/sass/bootstrap/lms-main.scss b/lms/static/sass/bootstrap/lms-main.scss index 935fea19cfec881c04ac09b6f15009156e352d30..404aba2d2a88194bd92aa953945fa1919433dde7 100644 --- a/lms/static/sass/bootstrap/lms-main.scss +++ b/lms/static/sass/bootstrap/lms-main.scss @@ -28,6 +28,7 @@ $static-path: '../..'; @import 'features/course-duration-limits'; @import 'features/enterprise-learner-portal-banner'; @import 'features/first-purchase-banner'; +@import 'features/next-up-banner'; // Individual Pages @import "views/program-marketing-page"; diff --git a/lms/static/sass/features/_next-up-banner.scss b/lms/static/sass/features/_next-up-banner.scss new file mode 100644 index 0000000000000000000000000000000000000000..aff36638f3a004829dc39f45b3eb017bce3a0980 --- /dev/null +++ b/lms/static/sass/features/_next-up-banner.scss @@ -0,0 +1,29 @@ +// Up next banner +.next-up-banner { + display: none; // Required to be none for Optimizely experiment in AA-29 + background-color: #d8edf8; + color: #393f43; + font-size: 16px; + border: 1px solid #bbdff2; + border-radius: 4px; + box-sizing: border-box; + line-height: 1.5; + margin: $baseline auto; + padding: 20px; + + a:not(.btn) { + font-weight: bold; + text-decoration: underline; + color: #006ea9; + + &:hover { + color: darken(#006ea9, 15%); + text-decoration: underline; + } + } + + & + .page-content { + margin-top: 0; + padding-top: 0; + } +} diff --git a/openedx/features/course_experience/templates/course_experience/course-home-fragment.html b/openedx/features/course_experience/templates/course_experience/course-home-fragment.html index 1c4275e87d4d7bd08774c76b5cf5bf8b6e60aaa8..b8e2e53a32f679cc3fae81e096d7ae9ef5d5f085 100644 --- a/openedx/features/course_experience/templates/course_experience/course-home-fragment.html +++ b/openedx/features/course_experience/templates/course_experience/course-home-fragment.html @@ -63,6 +63,9 @@ from openedx.features.course_experience.course_tools import HttpMethod </header> <div class="page-content"> <div class="page-content-main"> + % if next_up_banner_fragment: + ${HTML(next_up_banner_fragment.body_html())} + % endif % if offer_banner_fragment: ${HTML(offer_banner_fragment.content)} % endif diff --git a/openedx/features/course_experience/templates/course_experience/next-up-banner-fragment.html b/openedx/features/course_experience/templates/course_experience/next-up-banner-fragment.html new file mode 100644 index 0000000000000000000000000000000000000000..63762d523301afd522b9e8aa24ee984c7110e4b3 --- /dev/null +++ b/openedx/features/course_experience/templates/course_experience/next-up-banner-fragment.html @@ -0,0 +1,13 @@ +## mako + +<%page expression_filter="h"/> + +<%! +from django.utils.translation import ugettext as _ +%> + +<div class="next-up-banner"> + <b>${_("Get started on what's next: ")}</b> + <a href="${resume_course_url}">${assignment_title}</a> + (${assignment_duration}) +</div> \ No newline at end of file diff --git a/openedx/features/course_experience/views/course_home.py b/openedx/features/course_experience/views/course_home.py index 488452df093dc67df42cc0ac6d18f1bff12513a7..38eb8bdb823ce52b91f68e250ed7f6888e479bae 100644 --- a/openedx/features/course_experience/views/course_home.py +++ b/openedx/features/course_experience/views/course_home.py @@ -48,6 +48,7 @@ from .course_home_messages import CourseHomeMessageFragmentView from .course_outline import CourseOutlineFragmentView from .course_sock import CourseSockFragmentView from .latest_update import LatestUpdateFragmentView +from .next_up_banner import NextUpBannerFragmentView from .welcome_message import WelcomeMessageFragmentView EMPTY_HANDOUTS_HTML = u'<ol></ol>' @@ -88,10 +89,11 @@ class CourseHomeFragmentView(EdxFragmentView): """ Returns information relevant to resume course functionality. - Returns a tuple: (has_visited_course, resume_course_url) - has_visited_course: True if the user has ever visted the course, False otherwise. + Returns a tuple: (has_visited_course, resume_course_url, resume_course_title) + has_visited_course: True if the user has ever visited the course, False otherwise. resume_course_url: The URL of the 'resume course' block if the user has visited the course, otherwise the URL of the course root. + resume_course_title: The display_name of the resume course block, otherwise the display_name of course root """ course_outline_root_block = get_course_outline_block_tree(request, course_id, request.user) @@ -99,10 +101,12 @@ class CourseHomeFragmentView(EdxFragmentView): has_visited_course = bool(resume_block) if resume_block: resume_course_url = resume_block['lms_web_url'] + resume_course_title = resume_block['display_name'] else: resume_course_url = course_outline_root_block['lms_web_url'] if course_outline_root_block else None + resume_course_title = course_outline_root_block['display_name'] if course_outline_root_block else None - return has_visited_course, resume_course_url + return has_visited_course, resume_course_url, resume_course_title def _get_course_handouts(self, request, course): """ @@ -141,6 +145,7 @@ class CourseHomeFragmentView(EdxFragmentView): update_message_fragment = None course_sock_fragment = None offer_banner_fragment = None + next_up_banner_fragment = None course_expiration_fragment = None has_visited_course = None resume_course_url = None @@ -162,8 +167,11 @@ class CourseHomeFragmentView(EdxFragmentView): course_sock_fragment = CourseSockFragmentView().render_to_fragment( request, course=course_overview, **kwargs ) - has_visited_course, resume_course_url = self._get_resume_course_info(request, course_id) + has_visited_course, resume_course_url, resume_course_title = self._get_resume_course_info( + request, course_id + ) handouts_html = self._get_course_handouts(request, course) + offer_banner_fragment = get_first_purchase_offer_banner_fragment( request.user, course_overview @@ -172,6 +180,11 @@ class CourseHomeFragmentView(EdxFragmentView): request.user, course_overview ) + + next_up_banner_fragment = NextUpBannerFragmentView().render_to_fragment( + assignment_title=resume_course_title, resume_course_url=resume_course_url, assignment_duration='10 min' + ) + elif allow_public_outline or allow_public: outline_fragment = CourseOutlineFragmentView().render_to_fragment( request, course_id=course_id, user_is_enrolled=False, **kwargs @@ -228,6 +241,7 @@ class CourseHomeFragmentView(EdxFragmentView): 'course_home_message_fragment': course_home_message_fragment, 'offer_banner_fragment': offer_banner_fragment, 'course_expiration_fragment': course_expiration_fragment, + 'next_up_banner_fragment': next_up_banner_fragment, 'has_visited_course': has_visited_course, 'resume_course_url': resume_course_url, 'course_tools': course_tools, diff --git a/openedx/features/course_experience/views/next_up_banner.py b/openedx/features/course_experience/views/next_up_banner.py new file mode 100644 index 0000000000000000000000000000000000000000..d10675814155039629a9133ccd6f11ce67a00dbf --- /dev/null +++ b/openedx/features/course_experience/views/next_up_banner.py @@ -0,0 +1,27 @@ +""" +View logic for handling course messages. +""" + +from django.template.loader import render_to_string +from web_fragments.fragment import Fragment + +from openedx.core.djangoapps.plugin_api.views import EdxFragmentView + + +class NextUpBannerFragmentView(EdxFragmentView): + """ + A fragment that displays an up next banner with a call to action to resume the course. + """ + + # pylint: disable=arguments-differ + def render_to_fragment(self, assignment_title, resume_course_url, assignment_duration='10 mins'): + """ + Renders an up next banner fragment with the provided assignment title, duration, and a link to the URL. + """ + context = { + 'assignment_title': assignment_title, + 'resume_course_url': resume_course_url, + 'assignment_duration': assignment_duration, + } + html = render_to_string('course_experience/next-up-banner-fragment.html', context) + return Fragment(html)