From fd397964b39b67ca6125629cbe7c7062acf54c8c Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Mon, 9 Nov 2015 17:15:11 -0500 Subject: [PATCH 01/11] Styling and template changes for the Course Home page. ECOM-2810 --- lms/djangoapps/courseware/date_summary.py | 5 +- lms/djangoapps/survey/tests/test_views.py | 3 +- lms/static/sass/_developer.scss | 25 +++--- lms/static/sass/base/_layouts.scss | 7 ++ lms/static/sass/base/_variables.scss | 2 +- lms/static/sass/course/_info.scss | 61 +++++++++---- lms/static/sass/course/base/_base.scss | 3 +- .../course/layout/_courseware_header.scss | 40 +++++---- lms/static/sass/shared/_header.scss | 89 +++++-------------- lms/templates/courseware/date_summary.html | 2 +- lms/templates/courseware/info.html | 55 ++++++------ lms/templates/navigation-edx.html | 10 ++- lms/templates/navigation.html | 12 ++- .../user_api/accounts/tests/test_views.py | 5 ++ 14 files changed, 172 insertions(+), 147 deletions(-) diff --git a/lms/djangoapps/courseware/date_summary.py b/lms/djangoapps/courseware/date_summary.py index 20a9a2fca7..59448608cf 100644 --- a/lms/djangoapps/courseware/date_summary.py +++ b/lms/djangoapps/courseware/date_summary.py @@ -186,7 +186,10 @@ class VerifiedUpgradeDeadlineDate(DateSummary): """ css_class = 'verified-upgrade-deadline' title = _('Verification Upgrade Deadline') - description = _('You are still eligible to upgrade to a Verified Certificate!') + description = _( + 'You are still eligible to upgrade to a Verified Certificate! ' + 'Pursue it to highlight the knowledge and skills you gain in this course.' + ) link_text = _('Upgrade to Verified Certificate') @property diff --git a/lms/djangoapps/survey/tests/test_views.py b/lms/djangoapps/survey/tests/test_views.py index 66f4c3f0c7..ebd16373ff 100644 --- a/lms/djangoapps/survey/tests/test_views.py +++ b/lms/djangoapps/survey/tests/test_views.py @@ -11,6 +11,7 @@ from django.core.urlresolvers import reverse from survey.models import SurveyForm, SurveyAnswer +from student.tests.factories import UserFactory from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase @@ -29,7 +30,7 @@ class SurveyViewsTests(ModuleStoreTestCase): # Create two accounts self.password = 'abc' - self.student = User.objects.create_user('student', 'student@test.com', self.password) + self.student = UserFactory.create(username='student', email='student@test.com', password=self.password) self.test_survey_name = 'TestSurvey' self.test_form = ''' diff --git a/lms/static/sass/_developer.scss b/lms/static/sass/_developer.scss index d1d8102dca..5570d4a40b 100644 --- a/lms/static/sass/_developer.scss +++ b/lms/static/sass/_developer.scss @@ -231,40 +231,41 @@ .date-summary-container { .date-summary { @include clearfix; - margin-top: $baseline/2; - margin-bottom: $baseline/2; padding: 10px; - background-color: $gray-l4; @include border-left(3px solid $gray-l3); + .heading { + @extend %t-title7; + color: $gray-d2; + } + .description { margin-top: $baseline/2; margin-bottom: $baseline/2; display: inline-block; - color: $lighter-base-font-color; - font-size: 80%; + color: $gray-d1; + @extend %t-title8; } .date-summary-link { - @include float(right); - font-size: 80%; + @extend %t-title8; font-weight: $font-semibold; a { - color: $base-font-color; + color: $link-color; + font-weight: normal; } } .date { - color: $lighter-base-font-color; - font-size: 80%; + color: $gray-d1; + @extend %t-title9; } &-todays-date { @include border-left(3px solid $blue); .heading { - font-weight: $font-regular; - font-size: 80%; + @extend %t-title8; } } diff --git a/lms/static/sass/base/_layouts.scss b/lms/static/sass/base/_layouts.scss index 01afb7624d..128f14f526 100644 --- a/lms/static/sass/base/_layouts.scss +++ b/lms/static/sass/base/_layouts.scss @@ -32,9 +32,16 @@ body.view-in-course { .wrapper-course-material .course-material, .wrapper-preview-menu .preview-menu { width: auto; + } + + .wrapper-preview-menu .preview-menu { padding: 15px 2%; } + .wrapper-course-material .course-material { + padding: ($baseline/2) 0 0 0; + } + .wrapper-course-material .course-material .course-tabs { padding: 0; } diff --git a/lms/static/sass/base/_variables.scss b/lms/static/sass/base/_variables.scss index f72403bd37..c7211d9b86 100644 --- a/lms/static/sass/base/_variables.scss +++ b/lms/static/sass/base/_variables.scss @@ -514,7 +514,7 @@ $light-gray: rgb(221, 221, 221) !default; $dark-gray: rgb(51, 51, 51) !default; $border-color: rgb(200, 200, 200) !default; $sidebar-color: rgb(246, 246, 246) !default; -$outer-border-color: rgb(170, 170, 170); +$outer-border-color: $gray-l3; $light-gray: rgb(221,221,221) !default; // used by descriptor css diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index 0bbca1d757..8ccef38430 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -1,11 +1,39 @@ +.home-wrapper { + max-width: 1180px; + margin: 0 auto; + + .home { + margin: $baseline; + + .page-title { + margin-bottom: 5px; + color: $dark-gray1; + font-size: 24px; + } + + .page-subtitle { + color: $dark-gray2; + font-size: 14px; + text-transform: none; + } + } +} + div.info-wrapper { + background-color: rgb(252, 252, 252); + section.updates { @extend .content; + @include padding-left($baseline); line-height: lh(); - > h1 { - @extend .top-header; + h1 { + @include text-align(left); + @extend %t-strong; + @extend %t-title6; + margin-bottom: $baseline; + font-style: normal; } > p { @@ -19,10 +47,10 @@ div.info-wrapper { > li,article { @extend .clearfix; - border-bottom: 1px solid lighten($border-color, 10%); + padding: $baseline; list-style-type: none; margin-bottom: lh(1.5); - padding-bottom: lh(.75); + background-color: $white; ol, ul { ol,ul { @@ -31,8 +59,9 @@ div.info-wrapper { } h2 { - font-size: $body-font-size; - font-weight: bold; + @extend %t-title9; + margin-bottom: ($baseline/4); + text-transform: none; background: url('#{$static-path}/images/calendar-icon.png') 0 center no-repeat; padding-left: $baseline; } @@ -81,8 +110,7 @@ div.info-wrapper { padding: 20px 30px; margin: 0; @extend .sidebar; - border-radius: 0 4px 4px 0; - @include border-left(1px solid #ddd); + background: rgba(0, 0, 0, 0); box-shadow: none; font-size: 14px; @@ -97,15 +125,14 @@ div.info-wrapper { h1 { @include text-align(left); + @extend %t-strong; + @extend %t-title6; margin-bottom: 0; padding: 12px 26px 20px 0; - font-size: 18px; font-style: normal; - font-weight: bold; } ul { - background-color: #f6f6f6; margin-bottom: 14px; } @@ -119,7 +146,8 @@ div.info-wrapper { padding: 0; color: $link-color; - &:hover, &:focus { + &:hover, + &:focus { background: transparent; } } @@ -154,7 +182,8 @@ div.info-wrapper { display: inline-block; padding: 0; - &:hover, &:focus { + &:hover, + &:focus { background: transparent; } } @@ -175,7 +204,8 @@ div.info-wrapper { display: inline-block; padding: 0; - &:hover, &:focus { + &:hover, + &:focus { background: transparent; } } @@ -191,7 +221,8 @@ div.info-wrapper { position: absolute; width: 100%; - &:hover, &:focus { + &:hover, + &:focus { opacity: 0.6; filter: alpha(opacity=60); diff --git a/lms/static/sass/course/base/_base.scss b/lms/static/sass/course/base/_base.scss index 7aba39d2b6..775a77995a 100644 --- a/lms/static/sass/course/base/_base.scss +++ b/lms/static/sass/course/base/_base.scss @@ -24,8 +24,7 @@ display: table; table-layout: fixed; width: 100%; - border-radius: 3px; - border: 1px solid $outer-border-color; + border: 1px solid $border-color-2; background: $container-bg; box-shadow: 0 1px 2px $shadow-l2; } diff --git a/lms/static/sass/course/layout/_courseware_header.scss b/lms/static/sass/course/layout/_courseware_header.scss index 4b01542e8c..b88275cbbc 100644 --- a/lms/static/sass/course/layout/_courseware_header.scss +++ b/lms/static/sass/course/layout/_courseware_header.scss @@ -15,48 +15,50 @@ ol.course-tabs { @include border-top-radius(4px); @include clearfix(); - @include margin-left(10px); padding: ($baseline*0.75) 0 ($baseline*0.75) 0; li { @include float(left); list-style: none; - margin-right: 6px; &.prominent { margin-right: 16px; - background: rgba(255, 255, 255, .5); + @include margin-right(16px); + background: rgba(255, 255, 255, 0.5); border-radius: 3px; } &.prominent + li { - padding-left: ($baseline*0.75); - border-left: 1px solid #333; + @include padding-left($baseline*0.75); + @include border-left(1px solid $gray-d3); } a { - border-radius: 3px; - color: #555; + @include padding(($baseline/2), ($baseline*0.75), 13px, ($baseline*0.75)); + @extend %t-title7; + @extend %t-strong; + border-bottom: 3px solid transparent; + color: $gray-d1; display: block; text-align: center; - padding: ($baseline/2) 13px 12px; - font-size: 14px; - font-weight: bold; text-decoration: none; // text-shadow: 0 1px 0 rgba(0, 0, 0, .4); - &:hover, &:focus { - color: #333; - background: rgba(255, 255, 255, .6); + &:hover, + &:focus { + color: $blue; + border-bottom: 3px solid $blue; } &.active { - // background: $shadow; - @include linear-gradient(top, rgba(0, 0, 0, .4), rgba(0, 0, 0, .25)); + border-bottom: 3px solid $gray-d4; background-color: transparent; - box-shadow: 0 1px 0 rgba(255, 255, 255, .5), 0 1px 1px rgba(0, 0, 0, .3) inset; - color: $white; - text-shadow: 0 1px 0 rgba(0, 0, 0, .4); + color: $gray-d4; + + &:hover, + &:focus { + color: $gray-d4; + } } } } @@ -87,7 +89,7 @@ header.global.slim { } .guest .secondary { - margin-right: 0; + @include margin-right(0); } .guest .secondary a { diff --git a/lms/static/sass/shared/_header.scss b/lms/static/sass/shared/_header.scss index fd5bc1f996..d1dc31e846 100644 --- a/lms/static/sass/shared/_header.scss +++ b/lms/static/sass/shared/_header.scss @@ -115,8 +115,8 @@ header.global { .user { @include float(right); @extend %ui-print-excluded; - margin-top: ($baseline/4); padding-left: 0; + margin: 0; .settings-language-form { margin-top: 4px; @@ -135,7 +135,7 @@ header.global { &:last-child { > a { - padding: ($baseline/5) ($baseline/2); + padding: ($baseline/2); &.shopping-cart { border-radius: 4px; @@ -147,14 +147,19 @@ header.global { } } - a.user-link { - @include padding(5px, 2px, 10px, 10px); + .user-link { + padding: 0; position: relative; text-transform: none; font-size: 14px; font-weight: bold; letter-spacing: 0; + .user-image-frame { + max-width: ($baseline*2); + border-radius: 10%; + } + .icon { display: inline-block; @include float(left); @@ -163,35 +168,9 @@ header.global { color: $m-gray; } - .avatar { - - // CASE: right to left layout - - display: inline-block; - @include left(8px); - opacity: 0.5; - overflow: hidden; - top: 4px; - margin-top: 1px; - margin-right: 2px; - @include transition(all 0.15s linear 0s); - width: 19px; - } - div { - margin-top: 3px; - float: right; - margin-left: 4px; - } - div { - margin-top: 3px; - float: right; - margin-left: 4px; - } - - &:hover, &:focus { - .avatar { - opacity: 0.8; - } + .label-username { + @include float(right); + @include margin(($baseline*0.75), ($baseline/4), ($baseline*0.75), ($baseline*0.75)); } } @@ -471,7 +450,6 @@ header.global-new { .user { @include float(right); - margin-top: 4px; > .primary { display: block; @@ -489,7 +467,7 @@ header.global-new { > a { @include border-radius(0, 4px, 4px, 0); @include border-left(none); - padding: ($baseline/5) ($baseline/2); + padding: ($baseline/2) ($baseline/2); &.shopping-cart { border-radius: 4px; @@ -500,14 +478,19 @@ header.global-new { } } } - a.user-link { - @include padding(5px, 2px, 10px, 10px); + .user-link { + padding: 0; position: relative; text-transform: none; font-size: 14px; font-weight: bold; letter-spacing: 0; + .user-image-frame { + max-width: ($baseline/2); + border-radius: 10%; + } + .icon { display: inline-block; @include float(left); @@ -516,35 +499,9 @@ header.global-new { color: $m-gray; } - .avatar { - - // CASE: right to left layout - - display: inline-block; - @include left(8px); - opacity: 0.5; - overflow: hidden; - top: 4px; - margin-top: 1px; - margin-right: 2px; - @include transition(all 0.15s linear 0s); - width: 19px; - } - div { - margin-top: 3px; - float: right; - margin-left: 4px; - } - div { - margin-top: 3px; - float: right; - margin-left: 4px; - } - - &:hover, &:focus { - .avatar { - opacity: 0.8; - } + .label-username { + @include float(right); + @include margin(($baseline*0.75), ($baseline/4), ($baseline*0.75), ($baseline*0.75)); } } diff --git a/lms/templates/courseware/date_summary.html b/lms/templates/courseware/date_summary.html index 5fec70a850..754b78612d 100644 --- a/lms/templates/courseware/date_summary.html +++ b/lms/templates/courseware/date_summary.html @@ -11,7 +11,7 @@ % endif % if link and link_text: - ${link_text} + ${link_text} % endif diff --git a/lms/templates/courseware/info.html b/lms/templates/courseware/info.html index 71eedeb4f5..8cdf6b47be 100644 --- a/lms/templates/courseware/info.html +++ b/lms/templates/courseware/info.html @@ -14,50 +14,55 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration <%static:css group='style-course'/> - % if show_enroll_banner: -
-
-
-

${_("You are not enrolled yet")}

-
-

- ${_(u"You are not currently enrolled in this course. {link_start}Sign up now!{link_end}").format( - link_start=u"".format(url_to_enroll), - link_end=u"" - )} -

-
+
+
+
+

${_("You are not enrolled yet")}

+
+

+ ${_(u"You are not currently enrolled in this course. {link_start}Sign up now!{link_end}").format( + link_start=u"".format(url_to_enroll), + link_end=u"" + )} +

+
% endif <%include file="/courseware/course_navigation.html" args="active_page='info'" /> <%block name="js_extra"> - - + + <%block name="bodyclass">view-in-course view-course-info ${course.css_class or ''}
+
+
+

${_("Welcome to {org}'s {course_name}!").format(org=course.id.org, course_name=course.id.course) | h}

+

${course.display_name | h}

+
+
% if user.is_authenticated():
% if studio_url is not None and masquerade and masquerade.role == 'staff': - + % endif -

${_("Course Updates & News")}

+

${_("Course Updates and News")}

${get_course_info_section(request, masquerade_user, course, 'updates')}
diff --git a/lms/templates/navigation-edx.html b/lms/templates/navigation-edx.html index 0e005d0d1d..320dbbc953 100644 --- a/lms/templates/navigation-edx.html +++ b/lms/templates/navigation-edx.html @@ -5,6 +5,9 @@ from django.core.urlresolvers import reverse from django.utils.translation import ugettext as _ +from microsite_configuration.templatetags.microsite import platform_name +from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_urls_for_user + # App that handles subdomain specific branding from branding import api as branding_api # app that handles site status messages @@ -76,7 +79,12 @@ site_status_msg = get_site_status_msg(course_id)
  • ${_("Dashboard for:")} -
    ${user.username}
    + <% + username = user.username + profile_image_url = get_profile_image_urls_for_user(user)['medium'] + %> + ${_('Profile image for {username}').format(username=username)} +
    ${username}
  • diff --git a/lms/templates/navigation.html b/lms/templates/navigation.html index 75f7b1f612..962153e5d1 100644 --- a/lms/templates/navigation.html +++ b/lms/templates/navigation.html @@ -6,6 +6,9 @@ from django.core.urlresolvers import reverse from django.utils.translation import ugettext as _ from lms.djangoapps.ccx.overrides import get_current_ccx +from microsite_configuration import microsite +from microsite_configuration.templatetags.microsite import platform_name +from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_urls_for_user # App that handles subdomain specific branding from branding import api as branding_api @@ -101,9 +104,12 @@ site_status_msg = get_site_status_msg(course_id)
  • ${_("Dashboard for:")} -
    - ${user.username} -
    + <% + username = user.username + profile_image_url = get_profile_image_urls_for_user(user)['medium'] + %> + ${_('Profile image for {username}').format(username=username)} +
    ${username}
  • diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py index 2ce732e056..e525c14743 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py @@ -538,6 +538,11 @@ class TestAccountAPI(UserAPITestCase): verify_change_info(name_change_info[0], old_name, self.user.username, "Donald Duck",) verify_change_info(name_change_info[1], "Mickey Mouse", self.user.username, "Donald Duck") + @patch.dict( + 'openedx.core.djangoapps.user_api.accounts.image_helpers.PROFILE_IMAGE_SIZES_MAP', + {'full': 50, 'medium': 30, 'small': 10}, + clear=True + ) def test_patch_email(self): """ Test that the user can request an email change through the accounts API. From 96d030b6d9ddf90ef006537de93ad711699fe299 Mon Sep 17 00:00:00 2001 From: Ahsan Ulhaq Date: Tue, 10 Nov 2015 18:45:28 +0500 Subject: [PATCH 02/11] Reverse and Rename Courseware and Course Info Tabs ECOM-2678 --- cms/djangoapps/contentstore/features/pages.py | 8 +++--- .../tests/test_course_settings.py | 4 +-- common/lib/xmodule/xmodule/course_module.py | 4 +-- common/lib/xmodule/xmodule/tabs.py | 26 ++++++++++++++----- common/test/acceptance/pages/lms/tab_nav.py | 2 +- .../tests/lms/test_certificate_web_view.py | 2 +- common/test/acceptance/tests/lms/test_lms.py | 22 ++++++++-------- .../tests/lms/test_lms_acid_xblock.py | 6 ++--- .../tests/video/test_video_module.py | 2 +- lms/djangoapps/course_wiki/tests/tests.py | 4 +-- .../courseware/features/navigation.feature | 2 +- .../courseware/features/navigation.py | 8 +++--- lms/djangoapps/courseware/tabs.py | 4 +-- lms/djangoapps/courseware/tests/test_about.py | 4 +-- lms/djangoapps/courseware/tests/test_tabs.py | 5 ++-- lms/djangoapps/lms_xblock/mixin.py | 4 +-- lms/templates/courseware/course_about.html | 2 +- 17 files changed, 63 insertions(+), 46 deletions(-) diff --git a/cms/djangoapps/contentstore/features/pages.py b/cms/djangoapps/contentstore/features/pages.py index 464e76d7f8..bb3e113b64 100644 --- a/cms/djangoapps/contentstore/features/pages.py +++ b/cms/djangoapps/contentstore/features/pages.py @@ -98,25 +98,25 @@ def _verify_page_names(first, second): @step(u'the built-in pages are in the default order$') def built_in_pages_in_default_order(step): - expected_pages = ['Courseware', 'Course Info', 'Wiki', 'Progress'] + expected_pages = ['Home', 'Course', 'Wiki', 'Progress'] see_pages_in_expected_order(expected_pages) @step(u'the built-in pages are switched$') def built_in_pages_switched(step): - expected_pages = ['Courseware', 'Course Info', 'Progress', 'Wiki'] + expected_pages = ['Home', 'Course', 'Progress', 'Wiki'] see_pages_in_expected_order(expected_pages) @step(u'the pages are in the default order$') def pages_in_default_order(step): - expected_pages = ['Courseware', 'Course Info', 'Wiki', 'Progress', 'First', 'Empty'] + expected_pages = ['Home', 'Course', 'Wiki', 'Progress', 'First', 'Empty'] see_pages_in_expected_order(expected_pages) @step(u'the pages are switched$$') def pages_are_switched(step): - expected_pages = ['Courseware', 'Course Info', 'Progress', 'First', 'Empty', 'Wiki'] + expected_pages = ['Home', 'Course', 'Progress', 'First', 'Empty', 'Wiki'] see_pages_in_expected_order(expected_pages) diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index 2b300b74b4..5e732e64ca 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -936,8 +936,8 @@ class CourseMetadataEditingTest(CourseTestCase): self.assertNotIn("notes", course.advanced_modules) @ddt.data( - [{'type': 'courseware'}, {'type': 'course_info'}, {'type': 'wiki', 'is_hidden': True}], - [{'type': 'courseware', 'name': 'Courses'}, {'type': 'course_info', 'name': 'Info'}], + [{'type': 'course_info'}, {'type': 'courseware'}, {'type': 'wiki', 'is_hidden': True}], + [{'type': 'course_info', 'name': 'Home'}, {'type': 'courseware', 'name': 'Course'}], ) def test_course_tab_configurations(self, tab_list): self.course.tabs = tab_list diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index d2e7b12b42..3061f296a3 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -424,9 +424,9 @@ class CourseFields(object): ) has_children = True info_sidebar_name = String( - display_name=_("Course Info Sidebar Name"), + display_name=_("Course Home Sidebar Name"), help=_( - "Enter the heading that you want students to see above your course handouts on the Course Info page. " + "Enter the heading that you want students to see above your course handouts on the Course Home page. " "Your course handouts appear in the right panel of the page." ), scope=Scope.settings, default='Course Handouts') diff --git a/common/lib/xmodule/xmodule/tabs.py b/common/lib/xmodule/xmodule/tabs.py index 833e63f2a2..480c850d2b 100644 --- a/common/lib/xmodule/xmodule/tabs.py +++ b/common/lib/xmodule/xmodule/tabs.py @@ -305,8 +305,8 @@ class CourseTabList(List): """ course.tabs.extend([ - CourseTab.load('courseware'), - CourseTab.load('course_info') + CourseTab.load('course_info'), + CourseTab.load('courseware') ]) # Presence of syllabus tab is indicated by a course attribute @@ -389,6 +389,19 @@ class CourseTabList(List): else: yield tab + @classmethod + def upgrade_tabs(cls, tabs): + """ + Reverse and Rename Courseware to Course and Course Info to Home Tabs. + """ + if tabs and len(tabs) > 1: + if tabs[0].get('type') == 'courseware' and tabs[1].get('type') == 'course_info': + tabs[0], tabs[1] = tabs[1], tabs[0] + tabs[0]['name'] = _('Home') + tabs[1]['name'] = _('Course') + + return tabs + @classmethod def validate_tabs(cls, tabs): """ @@ -406,13 +419,13 @@ class CourseTabList(List): if len(tabs) < 2: raise InvalidTabsException("Expected at least two tabs. tabs: '{0}'".format(tabs)) - if tabs[0].get('type') != 'courseware': + if tabs[0].get('type') != 'course_info': raise InvalidTabsException( - "Expected first tab to have type 'courseware'. tabs: '{0}'".format(tabs)) + "Expected first tab to have type 'course_info'. tabs: '{0}'".format(tabs)) - if tabs[1].get('type') != 'course_info': + if tabs[1].get('type') != 'courseware': raise InvalidTabsException( - "Expected second tab to have type 'course_info'. tabs: '{0}'".format(tabs)) + "Expected second tab to have type 'courseware'. tabs: '{0}'".format(tabs)) # the following tabs should appear only once # TODO: don't import openedx capabilities from common @@ -455,6 +468,7 @@ class CourseTabList(List): """ Overrides the from_json method to de-serialize the CourseTab objects from a json-like representation. """ + self.upgrade_tabs(values) self.validate_tabs(values) tabs = [] for tab_dict in values: diff --git a/common/test/acceptance/pages/lms/tab_nav.py b/common/test/acceptance/pages/lms/tab_nav.py index 09949f2d91..74fc6ba4a7 100644 --- a/common/test/acceptance/pages/lms/tab_nav.py +++ b/common/test/acceptance/pages/lms/tab_nav.py @@ -21,7 +21,7 @@ class TabNavPage(PageObject): Navigate to the tab `tab_name`. """ - if tab_name not in ['Courseware', 'Course Info', 'Discussion', 'Wiki', 'Progress']: + if tab_name not in ['Course', 'Home', 'Discussion', 'Wiki', 'Progress']: self.warning("'{0}' is not a valid tab name".format(tab_name)) # The only identifier for individual tabs is the link href diff --git a/common/test/acceptance/tests/lms/test_certificate_web_view.py b/common/test/acceptance/tests/lms/test_certificate_web_view.py index f6f5acb6e7..b547a9d3a6 100644 --- a/common/test/acceptance/tests/lms/test_certificate_web_view.py +++ b/common/test/acceptance/tests/lms/test_certificate_web_view.py @@ -206,7 +206,7 @@ class CertificateProgressPageTest(UniqueCourseTest): Problems were added in the setUp """ self.course_info_page.visit() - self.tab_nav.go_to_tab('Courseware') + self.tab_nav.go_to_tab('Course') # Navigate to Test Subsection in Test Section Section self.course_nav.go_to_section('Test Section', 'Test Subsection') diff --git a/common/test/acceptance/tests/lms/test_lms.py b/common/test/acceptance/tests/lms/test_lms.py index 984403a83f..dfff902371 100644 --- a/common/test/acceptance/tests/lms/test_lms.py +++ b/common/test/acceptance/tests/lms/test_lms.py @@ -597,7 +597,7 @@ class HighLevelTabTest(UniqueCourseTest): # Navigate to the course info page from the progress page self.progress_page.visit() - self.tab_nav.go_to_tab('Course Info') + self.tab_nav.go_to_tab('Home') # Expect just one update self.assertEqual(self.course_info_page.num_updates, 1) @@ -667,13 +667,13 @@ class HighLevelTabTest(UniqueCourseTest): def test_courseware_nav(self): """ - Navigate to a particular unit in the courseware. + Navigate to a particular unit in the course. """ - # Navigate to the courseware page from the info page + # Navigate to the course page from the info page self.course_info_page.visit() - self.tab_nav.go_to_tab('Courseware') + self.tab_nav.go_to_tab('Course') - # Check that the courseware navigation appears correctly + # Check that the course navigation appears correctly EXPECTED_SECTIONS = { 'Test Section': ['Test Subsection'], 'Test Section 2': ['Test Subsection 2', 'Test Subsection 3'] @@ -862,7 +862,7 @@ class TooltipTest(UniqueCourseTest): Verify that tooltips are displayed when you hover over the sequence nav bar. """ self.course_info_page.visit() - self.tab_nav.go_to_tab('Courseware') + self.tab_nav.go_to_tab('Course') self.courseware_page.verify_tooltips_displayed() @@ -1011,7 +1011,7 @@ class ProblemExecutionTest(UniqueCourseTest): def test_python_execution_in_problem(self): # Navigate to the problem page self.course_info_page.visit() - self.tab_nav.go_to_tab('Courseware') + self.tab_nav.go_to_tab('Course') self.course_nav.go_to_section('Test Section', 'Test Subsection') problem_page = ProblemPage(self.browser) @@ -1061,14 +1061,14 @@ class EntranceExamTest(UniqueCourseTest): def test_entrance_exam_section(self): """ - Scenario: Any course that is enabled for an entrance exam, should have entrance exam chapter at courseware + Scenario: Any course that is enabled for an entrance exam, should have entrance exam chapter at course page. - Given that I am on the courseware page - When I view the courseware that has an entrance exam + Given that I am on the course page + When I view the course that has an entrance exam Then there should be an "Entrance Exam" chapter.' """ entrance_exam_link_selector = '.accordion .course-navigation .chapter .group-heading' - # visit courseware page and make sure there is not entrance exam chapter. + # visit course page and make sure there is not entrance exam chapter. self.courseware_page.visit() self.courseware_page.wait_for_page() self.assertFalse(element_has_text( diff --git a/common/test/acceptance/tests/lms/test_lms_acid_xblock.py b/common/test/acceptance/tests/lms/test_lms_acid_xblock.py index 14fd06b496..13ffbb9fa8 100644 --- a/common/test/acceptance/tests/lms/test_lms_acid_xblock.py +++ b/common/test/acceptance/tests/lms/test_lms_acid_xblock.py @@ -75,7 +75,7 @@ class XBlockAcidNoChildTest(XBlockAcidBase): """ self.course_info_page.visit() - self.tab_nav.go_to_tab('Courseware') + self.tab_nav.go_to_tab('Course') acid_block = AcidView(self.browser, '.xblock-student_view[data-block-type=acid]') self.validate_acid_block_view(acid_block) @@ -119,7 +119,7 @@ class XBlockAcidChildTest(XBlockAcidBase): """ self.course_info_page.visit() - self.tab_nav.go_to_tab('Courseware') + self.tab_nav.go_to_tab('Course') acid_parent_block = AcidView(self.browser, '.xblock-student_view[data-block-type=acid_parent]') self.validate_acid_parent_block_view(acid_parent_block) @@ -159,7 +159,7 @@ class XBlockAcidAsideTest(XBlockAcidBase): """ self.course_info_page.visit() - self.tab_nav.go_to_tab('Courseware') + self.tab_nav.go_to_tab('Course') acid_aside = AcidView(self.browser, '.xblock_asides-v1-student_view[data-block-type=acid_aside]') self.validate_acid_aside_view(acid_aside) diff --git a/common/test/acceptance/tests/video/test_video_module.py b/common/test/acceptance/tests/video/test_video_module.py index 6413686223..0ed1493e2c 100644 --- a/common/test/acceptance/tests/video/test_video_module.py +++ b/common/test/acceptance/tests/video/test_video_module.py @@ -132,7 +132,7 @@ class VideoBaseTest(UniqueCourseTest): self.auth_page.visit() self.user_info = self.auth_page.user_info self.course_info_page.visit() - self.tab_nav.go_to_tab('Courseware') + self.tab_nav.go_to_tab('Course') def _navigate_to_courseware_video_and_render(self): """ Wait for the video player to render """ diff --git a/lms/djangoapps/course_wiki/tests/tests.py b/lms/djangoapps/course_wiki/tests/tests.py index c7d79bc8aa..2d4285899b 100644 --- a/lms/djangoapps/course_wiki/tests/tests.py +++ b/lms/djangoapps/course_wiki/tests/tests.py @@ -103,8 +103,8 @@ class WikiRedirectTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): """ Ensure that the response has the course navigator. """ - self.assertContains(resp, "Course Info") - self.assertContains(resp, "courseware") + self.assertContains(resp, "Home") + self.assertContains(resp, "Course") @patch.dict("django.conf.settings.FEATURES", {'ALLOW_WIKI_ROOT_ACCESS': True}) def test_course_navigator(self): diff --git a/lms/djangoapps/courseware/features/navigation.feature b/lms/djangoapps/courseware/features/navigation.feature index 40a71d5615..98105e2a25 100644 --- a/lms/djangoapps/courseware/features/navigation.feature +++ b/lms/djangoapps/courseware/features/navigation.feature @@ -24,5 +24,5 @@ Feature: LMS.Navigate Course Given I am viewing a course with multiple sections When I navigate to a section And I see the content of the section - And I return to the courseware + And I return to the course Then I see that I was most recently in the subsection diff --git a/lms/djangoapps/courseware/features/navigation.py b/lms/djangoapps/courseware/features/navigation.py index 4520b410b0..7aa4006d54 100644 --- a/lms/djangoapps/courseware/features/navigation.py +++ b/lms/djangoapps/courseware/features/navigation.py @@ -1,5 +1,6 @@ # pylint: disable=missing-docstring # pylint: disable=redefined-outer-name +# pylint: disable=unused-argument from lettuce import world, step from common import course_location @@ -127,11 +128,12 @@ def then_i_see_the_content_of_the_sequence_item(step): wait_for_problem('Problem 6') -@step(u'I return to the courseware') -def and_i_return_to_the_courseware(step): +@step(u'I return to the course') +def and_i_return_to_the_course(step): world.visit('/') world.click_link("View Course") - world.click_link("Courseware") + course = 'a[href*="/courseware"]' + world.css_click(course) @step(u'I see that I was most recently in the subsection') diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index c4ddb6787e..721c967be0 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -28,7 +28,7 @@ class CoursewareTab(EnrolledTab): The main courseware view. """ type = 'courseware' - title = ugettext_noop('Courseware') + title = ugettext_noop('Course') priority = 10 view_name = 'courseware' is_movable = False @@ -40,7 +40,7 @@ class CourseInfoTab(CourseTab): The course info view. """ type = 'course_info' - title = ugettext_noop('Course Info') + title = ugettext_noop('Home') priority = 20 view_name = 'info' tab_id = 'info' diff --git a/lms/djangoapps/courseware/tests/test_about.py b/lms/djangoapps/courseware/tests/test_about.py index a26ccd8490..de00034ce3 100644 --- a/lms/djangoapps/courseware/tests/test_about.py +++ b/lms/djangoapps/courseware/tests/test_about.py @@ -105,7 +105,7 @@ class AboutTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, EventTrackingT resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertIn("You are enrolled in this course", resp.content) - self.assertIn("View Courseware", resp.content) + self.assertIn("View Course", resp.content) @override_settings(COURSE_ABOUT_VISIBILITY_PERMISSION="see_about_page") def test_visible_about_page_settings(self): @@ -478,7 +478,7 @@ class AboutPurchaseCourseTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertIn("You are enrolled in this course", resp.content) - self.assertIn("View Courseware", resp.content) + self.assertIn("View Course", resp.content) self.assertNotIn("Add buyme to Cart ($10 USD)", resp.content) def test_closed_enrollment(self): diff --git a/lms/djangoapps/courseware/tests/test_tabs.py b/lms/djangoapps/courseware/tests/test_tabs.py index 149bcbcdae..70c6d49858 100644 --- a/lms/djangoapps/courseware/tests/test_tabs.py +++ b/lms/djangoapps/courseware/tests/test_tabs.py @@ -483,9 +483,10 @@ class TabListTestCase(TabTestCase): [{'type': CoursewareTab.type}], # missing course_info [{'type': CoursewareTab.type}, {'type': 'discussion', 'name': 'fake_name'}], + [{'type': 'unknown_type'}], # incorrect order - [{'type': CourseInfoTab.type, 'name': 'fake_name'}, {'type': CoursewareTab.type}], - [{'type': 'unknown_type'}] + [{'type': 'discussion', 'name': 'fake_name'}, + {'type': CourseInfoTab.type, 'name': 'fake_name'}, {'type': CoursewareTab.type}], ] # tab types that should appear only once diff --git a/lms/djangoapps/lms_xblock/mixin.py b/lms/djangoapps/lms_xblock/mixin.py index a05fe5a8ec..e4eb865363 100644 --- a/lms/djangoapps/lms_xblock/mixin.py +++ b/lms/djangoapps/lms_xblock/mixin.py @@ -42,7 +42,7 @@ class LmsBlockMixin(XBlockMixin): scope=Scope.settings, ) chrome = String( - display_name=_("Courseware Chrome"), + display_name=_("Course Chrome"), # Translators: DO NOT translate the words in quotes here, they are # specific words for the acceptable values. help=_("Enter the chrome, or navigation tools, to use for the XBlock in the LMS. Valid values are: \n" @@ -55,7 +55,7 @@ class LmsBlockMixin(XBlockMixin): ) default_tab = String( display_name=_("Default Tab"), - help=_("Enter the tab that is selected in the XBlock. If not set, the Courseware tab is selected."), + help=_("Enter the tab that is selected in the XBlock. If not set, the Course tab is selected."), scope=Scope.settings, default=None, ) diff --git a/lms/templates/courseware/course_about.html b/lms/templates/courseware/course_about.html index d376099e85..b6cedfda38 100644 --- a/lms/templates/courseware/course_about.html +++ b/lms/templates/courseware/course_about.html @@ -126,7 +126,7 @@ from openedx.core.lib.courses import course_image_url ${_("You are enrolled in this course")} %if show_courseware_link: - ${_("View Courseware")} + ${_("View Course")} %endif From 786c4456ea3d002c4fcd786fefe8f593e942ca23 Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Thu, 5 Nov 2015 14:24:07 -0500 Subject: [PATCH 03/11] Last-accessed courseware on the home page. ECOM-2806 --- .../test/acceptance/tests/lms/test_library.py | 6 +-- .../courseware/features/navigation.feature | 7 --- .../courseware/features/navigation.py | 6 --- lms/djangoapps/courseware/field_overrides.py | 11 ++-- .../courseware/self_paced_overrides.py | 2 +- .../courseware/tests/test_course_info.py | 29 +++++++++++ lms/djangoapps/courseware/views.py | 51 ++++++++++++------- lms/static/sass/base/_variables.scss | 1 + lms/static/sass/course/_info.scss | 34 ++++++++++--- .../course/layout/_courseware_header.scss | 1 - lms/templates/courseware/info.html | 13 +++-- 11 files changed, 113 insertions(+), 48 deletions(-) diff --git a/common/test/acceptance/tests/lms/test_library.py b/common/test/acceptance/tests/lms/test_library.py index 7609cbcd3e..8b8dd1da31 100644 --- a/common/test/acceptance/tests/lms/test_library.py +++ b/common/test/acceptance/tests/lms/test_library.py @@ -127,9 +127,9 @@ class LibraryContentTestBase(UniqueCourseTest): Open library page in LMS """ self.courseware_page.visit() - paragraphs = self.courseware_page.q(css='.course-content p') - if paragraphs and "You were most recently in" in paragraphs.text[0]: - paragraphs[0].find_element_by_tag_name('a').click() + paragraphs = self.courseware_page.q(css='.course-content p').results + if not paragraphs: + self.courseware_page.q(css='.menu-item a').results[0].click() block_id = block_id if block_id is not None else self.lib_block.locator #pylint: disable=attribute-defined-outside-init self.library_content_page = LibraryContentXBlockWrapper(self.browser, block_id) diff --git a/lms/djangoapps/courseware/features/navigation.feature b/lms/djangoapps/courseware/features/navigation.feature index 98105e2a25..4c7320d7e5 100644 --- a/lms/djangoapps/courseware/features/navigation.feature +++ b/lms/djangoapps/courseware/features/navigation.feature @@ -19,10 +19,3 @@ Feature: LMS.Navigate Course When I navigate to an item in a sequence Then I see the content of the sequence item And a "seq_goto" browser event is emitted - - Scenario: I can return to the last section I visited - Given I am viewing a course with multiple sections - When I navigate to a section - And I see the content of the section - And I return to the course - Then I see that I was most recently in the subsection diff --git a/lms/djangoapps/courseware/features/navigation.py b/lms/djangoapps/courseware/features/navigation.py index 7aa4006d54..e0d426cc0e 100644 --- a/lms/djangoapps/courseware/features/navigation.py +++ b/lms/djangoapps/courseware/features/navigation.py @@ -136,12 +136,6 @@ def and_i_return_to_the_course(step): world.css_click(course) -@step(u'I see that I was most recently in the subsection') -def then_i_see_that_i_was_most_recently_in_the_subsection(step): - message = world.css_text('section.course-content > p') - assert_in("You were most recently in Test Subsection 2", message) - - def create_course(): world.clear_courses() world.scenario_dict['COURSE'] = world.CourseFactory.create( diff --git a/lms/djangoapps/courseware/field_overrides.py b/lms/djangoapps/courseware/field_overrides.py index 44207e1e71..9104fd5e33 100644 --- a/lms/djangoapps/courseware/field_overrides.py +++ b/lms/djangoapps/courseware/field_overrides.py @@ -228,11 +228,16 @@ class FieldOverrideProvider(object): @abstractmethod def enabled_for(self, course): # pragma no cover """ - Return True if this provider should be enabled for a given course + Return True if this provider should be enabled for a given course, + and False otherwise. - Return False otherwise + Concrete implementations are responsible for implementing this method. - Concrete implementations are responsible for implementing this method + Arguments: + course (CourseModule or None) + + Returns: + bool """ return False diff --git a/lms/djangoapps/courseware/self_paced_overrides.py b/lms/djangoapps/courseware/self_paced_overrides.py index c38b8961a6..e694705205 100644 --- a/lms/djangoapps/courseware/self_paced_overrides.py +++ b/lms/djangoapps/courseware/self_paced_overrides.py @@ -25,4 +25,4 @@ class SelfPacedDateOverrideProvider(FieldOverrideProvider): @classmethod def enabled_for(cls, course): """This provider is enabled for self-paced courses only.""" - return SelfPacedConfiguration.current().enabled and course.self_paced + return course is not None and course.self_paced and SelfPacedConfiguration.current().enabled diff --git a/lms/djangoapps/courseware/tests/test_course_info.py b/lms/djangoapps/courseware/tests/test_course_info.py index a67bc96fca..ae4940f0be 100644 --- a/lms/djangoapps/courseware/tests/test_course_info.py +++ b/lms/djangoapps/courseware/tests/test_course_info.py @@ -11,6 +11,7 @@ from django.core.urlresolvers import reverse from django.test.utils import override_settings from opaque_keys.edx.locations import SlashSeparatedCourseKey +from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration from util.date_utils import strftime_localized from xmodule.modulestore.tests.django_utils import ( ModuleStoreTestCase, @@ -92,6 +93,33 @@ class CourseInfoTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): response = self.client.get(url) self.assertEqual(response.status_code, 404) + def test_last_accessed_courseware_not_shown(self): + SelfPacedConfiguration(enable_course_home_improvements=True).save() + url = reverse('info', args=(unicode(self.course.id),)) + response = self.client.get(url) + self.assertNotIn('Jump back to where you were last:', response.content) + + def test_last_accessed_shown(self): + SelfPacedConfiguration(enable_course_home_improvements=True).save() + chapter = ItemFactory.create( + category="chapter", parent_location=self.course.location + ) + section = ItemFactory.create( + category='section', parent_location=chapter.location + ) + section_url = reverse( + 'courseware_section', + kwargs={ + 'section': section.url_name, + 'chapter': chapter.url_name, + 'course_id': self.course.id + } + ) + self.client.get(section_url) + info_url = reverse('info', args=(unicode(self.course.id),)) + info_page_response = self.client.get(info_url) + self.assertIn('Jump back to where you were last:', info_page_response.content) + class CourseInfoTestCaseCCX(SharedModuleStoreTestCase, LoginEnrollmentTestCase): """ @@ -169,6 +197,7 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest """ def setUp(self): + SelfPacedConfiguration(enabled=True).save() super(SelfPacedCourseInfoTestCase, self).setUp() self.instructor_paced_course = CourseFactory.create(self_paced=False) self.self_paced_course = CourseFactory.create(self_paced=True) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 31eaab5b2c..85473e46f1 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -72,6 +72,7 @@ from openedx.core.djangoapps.credit.api import ( ) from shoppingcart.models import CourseRegistrationCode from shoppingcart.utils import is_shopping_cart_enabled +from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration from student.models import UserTestGroup, CourseEnrollment from student.views import is_course_blocked from util.cache import cache, cache_if_anonymous @@ -549,8 +550,6 @@ def _index_bulk_op(request, course_key, chapter, section, position): context['fragment'] = section_module.render(STUDENT_VIEW, section_render_context) context['section_title'] = section_descriptor.display_name_with_default_escaped else: - # section is none, so display a message - studio_url = get_studio_url(course, 'course') prev_section = get_current_child(chapter_module) if prev_section is None: # Something went wrong -- perhaps this chapter has no sections visible to the user. @@ -559,22 +558,6 @@ def _index_bulk_op(request, course_key, chapter, section, position): course_module.position = None course_module.save() return redirect(reverse('courseware', args=[course.id.to_deprecated_string()])) - prev_section_url = reverse('courseware_section', kwargs={ - 'course_id': course_key.to_deprecated_string(), - 'chapter': chapter_descriptor.url_name, - 'section': prev_section.url_name - }) - context['fragment'] = Fragment(content=render_to_string( - 'courseware/welcome-back.html', - { - 'course': course, - 'studio_url': studio_url, - 'chapter_module': chapter_module, - 'prev_section': prev_section, - 'prev_section_url': prev_section_url - } - )) - result = render_to_response('courseware/courseware.html', context) except Exception as e: @@ -729,6 +712,14 @@ def course_info(request, course_id): 'url_to_enroll': url_to_enroll, } + # Get the URL of the user's last position in order to display the 'where you were last' message + context['last_accessed_courseware'] = None + if SelfPacedConfiguration.current().enable_course_home_improvements: + (section_module, section_url) = get_last_accessed_courseware(course, request) + if section_module is not None and section_url is not None: + context['last_accessed_courseware'] = section_module + context['last_accessed_url'] = section_url + now = datetime.now(UTC()) effective_start = _adjust_start_date_for_beta_testers(user, course, course_key) if not in_preview_mode() and staff_access and now < effective_start: @@ -739,6 +730,30 @@ def course_info(request, course_id): return render_to_response('courseware/info.html', context) +def get_last_accessed_courseware(course, request): + """ + Return a pair of the last-accessed courseware for this request's + user, and a URL for that module. + """ + field_data_cache = FieldDataCache.cache_for_descriptor_descendents( + course.id, request.user, course, depth=2 + ) + course_module = get_module_for_descriptor( + request.user, request, course, field_data_cache, course.id, course=course + ) + chapter_module = get_current_child(course_module) + if chapter_module is not None: + section_module = get_current_child(chapter_module) + if section_module is not None: + url = reverse('courseware_section', kwargs={ + 'course_id': unicode(course.id), + 'chapter': chapter_module.url_name, + 'section': section_module.url_name + }) + return (section_module, url) + return (None, None) + + @ensure_csrf_cookie @ensure_valid_course_key def static_tab(request, course_id, tab_slug): diff --git a/lms/static/sass/base/_variables.scss b/lms/static/sass/base/_variables.scss index c7211d9b86..6e68f74e90 100644 --- a/lms/static/sass/base/_variables.scss +++ b/lms/static/sass/base/_variables.scss @@ -465,6 +465,7 @@ $courseware-navigation-color: $blue !default; $homepage__header--gradient__color--alpha: lighten($gray, 15%) !default; $homepage__header--gradient__color--bravo: saturate($gray, 30%) !default; $homepage__header--background: lighten($gray, 15%) !default; +$homepage-background: rgb(252, 252, 252); $course-card-height: ($baseline*18) !default; $course-image-height: ($baseline*8) !default; $course-info-height: ($baseline*10) !default; diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index 8ccef38430..7831d5d749 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -1,9 +1,13 @@ -.home-wrapper { - max-width: 1180px; +.home { + @include clearfix(); + max-width: 1140px; margin: 0 auto; + padding: $baseline $baseline ($baseline/2) $baseline; - .home { - margin: $baseline; + .page-header-main { + display: inline-block; + width: flex-grid(8, 12); + margin: 0; .page-title { margin-bottom: 5px; @@ -17,11 +21,29 @@ text-transform: none; } } + + .page-header-secondary { + display: inline-block; + width: flex-grid(4, 12); + margin: 0; + padding: ($baseline/2) ($baseline*0.75); + border: 1px solid $blue; + background-color: $homepage-background; + @extend %t-title8; + color: $blue-d1; + @extend %cont-truncated; + vertical-align: text-bottom; + + .last-accessed-message { + display: inline-block; + @include margin-left($baseline*0.75); + } + } } div.info-wrapper { - background-color: rgb(252, 252, 252); + background-color: $homepage-background; section.updates { @extend .content; @@ -63,7 +85,7 @@ div.info-wrapper { margin-bottom: ($baseline/4); text-transform: none; background: url('#{$static-path}/images/calendar-icon.png') 0 center no-repeat; - padding-left: $baseline; + @include padding-left($baseline); } section.update-description { diff --git a/lms/static/sass/course/layout/_courseware_header.scss b/lms/static/sass/course/layout/_courseware_header.scss index b88275cbbc..71440c9727 100644 --- a/lms/static/sass/course/layout/_courseware_header.scss +++ b/lms/static/sass/course/layout/_courseware_header.scss @@ -22,7 +22,6 @@ list-style: none; &.prominent { - margin-right: 16px; @include margin-right(16px); background: rgba(255, 255, 255, 0.5); border-radius: 3px; diff --git a/lms/templates/courseware/info.html b/lms/templates/courseware/info.html index 8cdf6b47be..9891959aa3 100644 --- a/lms/templates/courseware/info.html +++ b/lms/templates/courseware/info.html @@ -45,11 +45,18 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration <%block name="bodyclass">view-in-course view-course-info ${course.css_class or ''}
    -
    -
    +
    +

    ${_("Welcome to {org}'s {course_name}!").format(org=course.id.org, course_name=course.id.course) | h}

    ${course.display_name | h}

    -
    +
    + % if last_accessed_courseware: +
    + +

    ${_("Jump back to where you were last:")}

    + ${last_accessed_courseware.display_name | h} +
    + % endif
  • % if user.is_authenticated(): From c22677c1712bffd57d9abc1bdbd0d88a36b92ead Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Mon, 16 Nov 2015 14:20:59 -0500 Subject: [PATCH 04/11] Move Course Handouts to Course Resources and Tools. The existing course handouts section is moved underneath Course Updates and News, with updated styling. ECOM-2808 --- common/lib/xmodule/xmodule/course_module.py | 4 +- .../pages/studio/settings_advanced.py | 2 +- lms/static/sass/course/_info.scss | 197 +++++------------- lms/templates/courseware/info.html | 72 +++---- 4 files changed, 91 insertions(+), 184 deletions(-) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 3061f296a3..fd999687b6 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -423,13 +423,13 @@ class CourseFields(object): scope=Scope.settings ) has_children = True - info_sidebar_name = String( + info_section_name = String( display_name=_("Course Home Sidebar Name"), help=_( "Enter the heading that you want students to see above your course handouts on the Course Home page. " "Your course handouts appear in the right panel of the page." ), - scope=Scope.settings, default='Course Handouts') + scope=Scope.settings, default=_('Course Resources and Tools')) show_timezone = Boolean( help=_( "True if timezones should be shown on dates in the courseware. " diff --git a/common/test/acceptance/pages/studio/settings_advanced.py b/common/test/acceptance/pages/studio/settings_advanced.py index 10b3939c08..2962dbb876 100644 --- a/common/test/acceptance/pages/studio/settings_advanced.py +++ b/common/test/acceptance/pages/studio/settings_advanced.py @@ -176,7 +176,7 @@ class AdvancedSettingsPage(CoursePage): 'advertised_start', 'announcement', 'display_name', - 'info_sidebar_name', + 'info_section_name', 'is_new', 'issue_badges', 'max_student_enrollments_allowed', diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index 7831d5d749..afa626fa34 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -44,6 +44,7 @@ div.info-wrapper { background-color: $homepage-background; + border-top: none; section.updates { @extend .content; @@ -54,7 +55,8 @@ div.info-wrapper { @include text-align(left); @extend %t-strong; @extend %t-title6; - margin-bottom: $baseline; + margin-bottom: 0; + @include padding(12px, 26px, 20px, 0); font-style: normal; } @@ -72,7 +74,6 @@ div.info-wrapper { padding: $baseline; list-style-type: none; margin-bottom: lh(1.5); - background-color: $white; ol, ul { ol,ul { @@ -128,24 +129,20 @@ div.info-wrapper { } } - section.handouts { - padding: 20px 30px; + section.course-dates { + @include padding(32px, 30px, 20px, 30px); margin: 0; @extend .sidebar; background: rgba(0, 0, 0, 0); box-shadow: none; font-size: 14px; - a { - color: $link-color; - } - &:after { left: -1px; right: auto; } - h1 { + .course-info-heading { @include text-align(left); @extend %t-strong; @extend %t-title6; @@ -154,146 +151,60 @@ div.info-wrapper { font-style: normal; } + @media print { + background: transparent !important; + } + } + + section.handouts { + margin: $baseline; + border-top: ($baseline/4) solid $gray-l5; + + .course-info-heading { + @include text-align(left); + @extend %t-strong; + @extend %t-title6; + margin-top: $baseline; + margin-bottom: ($baseline/2); + padding: 0; + font-style: normal; + } + + h1 { + @include text-align(left); + @extend %t-strong; + @extend %t-title7; + margin-bottom: 0; + } + + a { + color: $link-color; + } + ul { - margin-bottom: 14px; + margin: ($baseline/2) 0; + padding: 0; + list-style: none; + + > li { + margin: ($baseline/4) 0; + } } ol { - margin-bottom: 14px; - li { - @include text-align(left); + margin: 0; + padding: 0; + display: inline-block; + list-style: none; + @include font-size(14); - a { - display: block; - padding: 0; - color: $link-color; + > li { + margin: ($baseline/4) 0; + border-bottom: 2px solid $gray-l4; + } - &:hover, - &:focus { - background: transparent; - } - } - - &.expandable, - &.collapsable { - margin: 0 16px 14px 16px; - @include transition(all .2s linear 0s); - - h4 { - color: $link-color; - font-size: 1em; - font-weight: normal; - padding-left: 30px; - } - } - - &.collapsable { - background: $white; - border-radius: 3px; - padding: 14px 0; - box-shadow: 0 0 1px 1px $shadow-l1, 0 1px 3px rgba(0, 0, 0, .25); - - h4 { - margin-bottom: 16px; - } - } - - &.multiple { - - a { - display: inline-block; - padding: 0; - - &:hover, - &:focus { - background: transparent; - } - } - } - - ul { - background: none; - margin: 0; - - li { - border-bottom: 0; - border-top: 1px solid #e6e6e6; - font-size: 0.9em; - margin: 0; - padding: 15px 30px; - - a { - display: inline-block; - padding: 0; - - &:hover, - &:focus { - background: transparent; - } - } - } - } - - div.hitarea { - background-image: url('#{$static-path}/images/treeview-default.gif') no-repeat; - display: block; - height: 100%; - margin-left: 0; - max-height: 20px; - position: absolute; - width: 100%; - - &:hover, - &:focus { - opacity: 0.6; - filter: alpha(opacity=60); - - + h4 { - @extend a:hover; - text-decoration: underline; - } - } - - &.expandable-hitarea { - background-position: -72px 0px; - } - - &.collapsable-hitarea { - background-position: -55px -23px; - } - } - - h3 { - border-bottom: 0; - box-shadow: none; - color: #888; - font-size: 1em; - margin-bottom: 0; - } - - p { - letter-spacing: 0; - margin: 0; - text-transform: none; - - a { - padding-right: 8px; - - &:before { - color: $gray-l3; - content: "•"; - display: inline-block; - padding-right: 8px; - } - - &:first-child { - &:before { - content: ""; - padding-right: 0; - } - } - } - } + li:last-child { + border: none; } } diff --git a/lms/templates/courseware/info.html b/lms/templates/courseware/info.html index 9891959aa3..e51d1b7c0f 100644 --- a/lms/templates/courseware/info.html +++ b/lms/templates/courseware/info.html @@ -34,15 +34,6 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration <%include file="/courseware/course_navigation.html" args="active_page='info'" /> -<%block name="js_extra"> - - - - <%block name="bodyclass">view-in-course view-course-info ${course.css_class or ''}
    @@ -59,37 +50,42 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration % endif
    +
    + % if user.is_authenticated(): +
    + % if studio_url is not None and masquerade and masquerade.role == 'staff': + + % endif +

    ${_("Course Updates and News")}

    + ${get_course_info_section(request, masquerade_user, course, 'updates')} +
    + % if SelfPacedConfiguration.current().enable_course_home_improvements: +
    +

    ${_("Important Course Dates")}

    + ${get_course_date_summary(course, user)} +
    + % endif + % else: +
    +

    ${_("Course Updates and News")}

    + ${get_course_info_section(request, masquerade_user, course, 'guest_updates')} +
    + % endif +
    % if user.is_authenticated(): -
    - % if studio_url is not None and masquerade and masquerade.role == 'staff': - - % endif - -

    ${_("Course Updates and News")}

    - ${get_course_info_section(request, masquerade_user, course, 'updates')} -
    -
    - % if SelfPacedConfiguration.current().enable_course_home_improvements: -

    ${_("Important Course Dates")}

    - ${get_course_date_summary(course, user)} - % endif - -

    ${_(course.info_sidebar_name)}

    - ${get_course_info_section(request, masquerade_user, course, 'handouts')} -
    +
    +

    ${_(course.info_section_name)}

    + ${get_course_info_section(request, masquerade_user, course, 'handouts')} +
    % else: -
    -

    ${_("Course Updates & News")}

    - ${get_course_info_section(request, masquerade_user, course, 'guest_updates')} -
    -
    -

    ${_("Course Handouts")}

    - ${get_course_info_section(request, masquerade_user, course, 'guest_handouts')} -
    +
    +

    ${_("Course Handouts")}

    + ${get_course_info_section(request, course, 'guest_handouts')} +
    % endif
    From 9b7ce7f3f2ddad01a01a23cb56153115654681f2 Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Thu, 19 Nov 2015 11:41:01 -0500 Subject: [PATCH 05/11] Lighten course tab font weight. --- lms/static/sass/course/layout/_courseware_header.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/static/sass/course/layout/_courseware_header.scss b/lms/static/sass/course/layout/_courseware_header.scss index 71440c9727..f3519cd4fd 100644 --- a/lms/static/sass/course/layout/_courseware_header.scss +++ b/lms/static/sass/course/layout/_courseware_header.scss @@ -35,7 +35,7 @@ a { @include padding(($baseline/2), ($baseline*0.75), 13px, ($baseline*0.75)); @extend %t-title7; - @extend %t-strong; + @extend %t-regular; border-bottom: 3px solid transparent; color: $gray-d1; display: block; From 75bef91e17c570b2be8db5622af972efb9965660 Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Tue, 24 Nov 2015 15:47:51 -0500 Subject: [PATCH 06/11] Fix query counts after Django 1.8 merge. --- lms/djangoapps/courseware/tests/test_course_info.py | 4 ++-- lms/djangoapps/teams/tests/test_views.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_course_info.py b/lms/djangoapps/courseware/tests/test_course_info.py index ae4940f0be..79f06d1f81 100644 --- a/lms/djangoapps/courseware/tests/test_course_info.py +++ b/lms/djangoapps/courseware/tests/test_course_info.py @@ -215,7 +215,7 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest self.assertEqual(resp.status_code, 200) def test_num_queries_instructor_paced(self): - self.fetch_course_info_with_queries(self.instructor_paced_course, 17, 4) + self.fetch_course_info_with_queries(self.instructor_paced_course, 19, 4) def test_num_queries_self_paced(self): - self.fetch_course_info_with_queries(self.self_paced_course, 17, 4) + self.fetch_course_info_with_queries(self.self_paced_course, 19, 4) diff --git a/lms/djangoapps/teams/tests/test_views.py b/lms/djangoapps/teams/tests/test_views.py index ad50ac83a2..f2c946f96c 100644 --- a/lms/djangoapps/teams/tests/test_views.py +++ b/lms/djangoapps/teams/tests/test_views.py @@ -115,8 +115,8 @@ class TestDashboard(SharedModuleStoreTestCase): CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id) self.client.login(username=self.user.username, password=self.test_password) - # Check the query count on the dashboard With no teams - with self.assertNumQueries(22): + # Check the query count on the dashboard with no teams + with self.assertNumQueries(18): self.client.get(self.teams_url) # Create some teams @@ -131,7 +131,7 @@ class TestDashboard(SharedModuleStoreTestCase): team.add_user(self.user) # Check the query count on the dashboard again - with self.assertNumQueries(22): + with self.assertNumQueries(24): self.client.get(self.teams_url) def test_bad_course_id(self): From e75f7950d4c99b0fc5d99107d74bc853259e95ec Mon Sep 17 00:00:00 2001 From: Bill DeRusha Date: Tue, 17 Nov 2015 15:16:27 -0500 Subject: [PATCH 07/11] Updates XBlock renders its own template. Adds expand and collapse JS + jasmine tests. ECOM-2809 --- .../contentstore/course_info_model.py | 14 +---- .../views/tests/test_course_updates.py | 5 +- cms/templates/course_info.html | 2 +- common/lib/xmodule/xmodule/html_module.py | 52 +++++++++++++++---- .../courseware/toggle_element_visibility.js | 42 +++++++++++++++ .../fixtures/courseware/course_updates.html | 45 ++++++++++++++++ .../js/spec/courseware/updates_visibility.js | 36 +++++++++++++ lms/static/js/spec/main.js | 1 + lms/static/sass/course/_info.scss | 36 ++++++++++++- lms/templates/courseware/course_updates.html | 29 +++++++++++ lms/templates/courseware/info.html | 4 ++ .../user_api/accounts/image_helpers.py | 19 ++++--- 12 files changed, 249 insertions(+), 36 deletions(-) create mode 100644 lms/static/js/courseware/toggle_element_visibility.js create mode 100644 lms/static/js/fixtures/courseware/course_updates.html create mode 100644 lms/static/js/spec/courseware/updates_visibility.js create mode 100644 lms/templates/courseware/course_updates.html diff --git a/cms/djangoapps/contentstore/course_info_model.py b/cms/djangoapps/contentstore/course_info_model.py index 570763bd28..c5632b3373 100644 --- a/cms/djangoapps/contentstore/course_info_model.py +++ b/cms/djangoapps/contentstore/course_info_model.py @@ -160,25 +160,13 @@ def _get_index(passed_id=None): return 0 -def _get_html(course_updates_items): - """ - Method to create course_updates_html from course_updates items - """ - list_items = [] - for update in reversed(course_updates_items): - # filter course update items which have status "deleted". - if update.get("status") != CourseInfoModule.STATUS_DELETED: - list_items.append(u"

    {date}

    {content}
    ".format(**update)) - return u"
    {list_items}
    ".format(list_items="".join(list_items)) - - def save_course_update_items(location, course_updates, course_update_items, user=None): """ Save list of course_updates data dictionaries in new field ("course_updates.items") and html related to course update in 'data' ("course_updates.data") field. """ course_updates.items = course_update_items - course_updates.data = _get_html(course_update_items) + course_updates.data = "" # update db record modulestore().update_item(course_updates, user.id) diff --git a/cms/djangoapps/contentstore/views/tests/test_course_updates.py b/cms/djangoapps/contentstore/views/tests/test_course_updates.py index 94f92fe637..f9199c005a 100644 --- a/cms/djangoapps/contentstore/views/tests/test_course_updates.py +++ b/cms/djangoapps/contentstore/views/tests/test_course_updates.py @@ -173,9 +173,8 @@ class CourseUpdateTest(CourseTestCase): self.assertHTMLEqual(update_content, json.loads(resp.content)['content']) course_updates = modulestore().get_item(location) self.assertEqual(course_updates.items, [{u'date': update_date, u'content': update_content, u'id': 1}]) - # course_updates 'data' field should update accordingly - update_data = u"

    {date}

    {content}
    ".format(date=update_date, content=update_content) - self.assertEqual(course_updates.data, update_data) + # course_updates 'data' field should not update automatically + self.assertEqual(course_updates.data, '') # test delete course update item (soft delete) course_updates = modulestore().get_item(location) diff --git a/cms/templates/course_info.html b/cms/templates/course_info.html index 7fa84c264e..dc2d4640ce 100644 --- a/cms/templates/course_info.html +++ b/cms/templates/course_info.html @@ -27,7 +27,7 @@ from openedx.core.lib.js_utils import escape_json_dumps "${handouts_locator | escapejs}", "${base_asset_url}", ${escape_json_dumps(push_notification_enabled) | n} - ); + ); }); diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py index 14b6b19280..62fce42e3f 100644 --- a/common/lib/xmodule/xmodule/html_module.py +++ b/common/lib/xmodule/xmodule/html_module.py @@ -1,13 +1,14 @@ -import os -import sys -import re import copy -import logging -import textwrap -from lxml import etree -from path import Path as path +from datetime import datetime from fs.errors import ResourceNotFoundError +import logging +from lxml import etree +import os +from path import Path as path from pkg_resources import resource_string +import re +import sys +import textwrap import dogstats_wrapper as dog_stats_api from xmodule.util.misc import escape_html_characters @@ -75,10 +76,10 @@ class HtmlBlock(object): return Fragment(self.get_html()) def get_html(self): - """ - When we switch this to an XBlock, we can merge this with student_view, - but for now the XModule mixin requires that this method be defined. - """ + """ Returns html required for rendering XModule. """ + + # When we switch this to an XBlock, we can merge this with student_view, + # but for now the XModule mixin requires that this method be defined. # pylint: disable=no-member if self.system.anonymous_student_id: return self.data.replace("%%USER_ID%%", self.system.anonymous_student_id) @@ -417,6 +418,35 @@ class CourseInfoModule(CourseInfoFields, HtmlModuleMixin): # statuses STATUS_VISIBLE = 'visible' STATUS_DELETED = 'deleted' + TEMPLATE_DIR = 'courseware' + + @XBlock.supports("multi_device") + def student_view(self, _context): + """ + Return a fragment that contains the html for the student view + """ + return Fragment(self.get_html()) + + def get_html(self): + """ Returns html required for rendering XModule. """ + + # When we switch this to an XBlock, we can merge this with student_view, + # but for now the XModule mixin requires that this method be defined. + # pylint: disable=no-member + if self.data != "": + if self.system.anonymous_student_id: + return self.data.replace("%%USER_ID%%", self.system.anonymous_student_id) + return self.data + else: + course_updates = [item for item in self.items if item.get('status') == self.STATUS_VISIBLE] + course_updates.sort(key=lambda item: datetime.strptime(item['date'], '%B %d, %Y'), reverse=True) + + context = { + 'visible_updates': course_updates[:3], + 'hidden_updates': course_updates[3:], + } + + return self.system.render_template("{0}/course_updates.html".format(self.TEMPLATE_DIR), context) @XBlock.tag("detached") diff --git a/lms/static/js/courseware/toggle_element_visibility.js b/lms/static/js/courseware/toggle_element_visibility.js new file mode 100644 index 0000000000..b5dc438b0a --- /dev/null +++ b/lms/static/js/courseware/toggle_element_visibility.js @@ -0,0 +1,42 @@ +;(function (define) { + 'use strict'; + + define(["jquery"], + function ($) { + + return function () { + // define variables for code legibility + var toggleActionElements = $('.toggle-visibility-button'); + + var updateToggleActionText = function (targetElement, actionElement) { + var show_text = actionElement.data('show'); + var hide_text = actionElement.data('hide'); + + if (targetElement.is(":visible")) { + if (hide_text) { + actionElement.html(actionElement.data('hide')); + } else { + actionElement.hide(); + } + } else { + if (show_text) { + actionElement.html(actionElement.data('show')); + } + } + }; + + $.each(toggleActionElements, function (i, elem) { + var toggleActionElement = $(elem); + var toggleTargetElement = toggleActionElement.siblings('.toggle-visibility-element'); + + updateToggleActionText(toggleTargetElement, toggleActionElement); + + toggleActionElement.on('click', function (event) { + event.preventDefault(); + toggleTargetElement.toggleClass('hidden'); + updateToggleActionText(toggleTargetElement, toggleActionElement); + }); + }); + }; + }); +})(define || RequireJS.define); diff --git a/lms/static/js/fixtures/courseware/course_updates.html b/lms/static/js/fixtures/courseware/course_updates.html new file mode 100644 index 0000000000..51935ba02f --- /dev/null +++ b/lms/static/js/fixtures/courseware/course_updates.html @@ -0,0 +1,45 @@ +
    +
    +

    December 1, 2015

    + Hide +
    +

    Assignment 1

    +

    Please submit your first assignment before due date.

    +
    +
    +
    +

    December 1, 2015

    + Show +
    +

    Quiz 1

    +

    You have a quiz due on coming friday.

    +
    +
    +
    +

    November 26, 2015

    + Show + +
    +
    + + + Show Earlier Course Updates + diff --git a/lms/static/js/spec/courseware/updates_visibility.js b/lms/static/js/spec/courseware/updates_visibility.js new file mode 100644 index 0000000000..dc03954502 --- /dev/null +++ b/lms/static/js/spec/courseware/updates_visibility.js @@ -0,0 +1,36 @@ +define(['jquery', 'js/courseware/toggle_element_visibility'], + function ($, ToggleElementVisibility) { + 'use strict'; + + describe('show/hide with mouse click', function () { + + beforeEach(function() { + loadFixtures('js/fixtures/courseware/course_updates.html'); + /*jshint newcap: false */ + ToggleElementVisibility(); + /*jshint newcap: true */ + }); + + it('ensures update will hide on hide button click', function () { + var $shownUpdate = $('.toggle-visibility-element:not(.hidden)').first(); + $shownUpdate.siblings('.toggle-visibility-button').trigger('click'); + expect($shownUpdate).toHaveClass('hidden'); + }); + + it('ensures update will show on show button click', function () { + var $hiddenUpdate = $('.toggle-visibility-element.hidden').first(); + $hiddenUpdate.siblings('.toggle-visibility-button').trigger('click'); + expect($hiddenUpdate).not.toHaveClass('hidden'); + }); + + it('ensures old updates will show on button click', function () { + // on page load old updates will be hidden + var $oldUpdates = $('.toggle-visibility-element.old-updates'); + expect($oldUpdates).toHaveClass('hidden'); + + // on click on show earlier update button old updates will be shown + $('.toggle-visibility-button.show-older-updates').trigger('click'); + expect($oldUpdates).not.toHaveClass('hidden'); + }); + }); + }); diff --git a/lms/static/js/spec/main.js b/lms/static/js/spec/main.js index 427b066f55..3d0ae676d1 100644 --- a/lms/static/js/spec/main.js +++ b/lms/static/js/spec/main.js @@ -708,6 +708,7 @@ 'lms/include/js/spec/edxnotes/collections/notes_spec.js', 'lms/include/js/spec/search/search_spec.js', 'lms/include/js/spec/navigation_spec.js', + 'lms/include/js/spec/courseware/updates_visibility.js', 'lms/include/js/spec/discovery/collections/filters_spec.js', 'lms/include/js/spec/discovery/models/course_card_spec.js', 'lms/include/js/spec/discovery/models/course_directory_spec.js', diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index afa626fa34..59f0fdfbfe 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -50,6 +50,8 @@ div.info-wrapper { @extend .content; @include padding-left($baseline); line-height: lh(); + width: 100%; + display: block; h1 { @include text-align(left); @@ -69,6 +71,25 @@ div.info-wrapper { margin-bottom: lh(); padding-left: 0; + .updates-article { + border-radius:3px; + background-color: $white; + border:1px solid transparent; + &:hover { + border: 1px solid $gray-l3; + } + } + + .show-older-updates { + @extend %btn-pl-white-base; + padding: ($baseline/2); + @include font-size(14); + width: 100%; + display: block; + text-align: center; + cursor: pointer; + } + > li,article { @extend .clearfix; padding: $baseline; @@ -81,12 +102,25 @@ div.info-wrapper { } } - h2 { + h2.date { @extend %t-title9; margin-bottom: ($baseline/4); text-transform: none; background: url('#{$static-path}/images/calendar-icon.png') 0 center no-repeat; @include padding-left($baseline); + @include float(left); + } + + .toggle-visibility-button { + @extend %t-title9; + @include float(right); + cursor: pointer; + } + + .toggle-visibility-element { + content:''; + display:block; + clear: both; } section.update-description { diff --git a/lms/templates/courseware/course_updates.html b/lms/templates/courseware/course_updates.html new file mode 100644 index 0000000000..6fc936b753 --- /dev/null +++ b/lms/templates/courseware/course_updates.html @@ -0,0 +1,29 @@ +<%! from django.utils.translation import ugettext as _ %> +
    +
    + % for index, update in enumerate(visible_updates): +
    + % if not update.get("is_error"): +

    ${update.get("date")}

    + + % endif +
    + ${update.get("content")} +
    +
    + % endfor +
    + + +% if len(hidden_updates) > 0: + +% endif +
    diff --git a/lms/templates/courseware/info.html b/lms/templates/courseware/info.html index e51d1b7c0f..dbc4b8d41b 100644 --- a/lms/templates/courseware/info.html +++ b/lms/templates/courseware/info.html @@ -34,6 +34,10 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration <%include file="/courseware/course_navigation.html" args="active_page='info'" /> +<%static:require_module module_name="js/courseware/toggle_element_visibility" class_name="ToggleElementVisibility"> + ToggleElementVisibility(); + + <%block name="bodyclass">view-in-course view-course-info ${course.css_class or ''}
    diff --git a/openedx/core/djangoapps/user_api/accounts/image_helpers.py b/openedx/core/djangoapps/user_api/accounts/image_helpers.py index 00de36609c..b204d6daf6 100644 --- a/openedx/core/djangoapps/user_api/accounts/image_helpers.py +++ b/openedx/core/djangoapps/user_api/accounts/image_helpers.py @@ -92,13 +92,18 @@ def get_profile_image_urls_for_user(user, request=None): dictionary of {size_display_name: url} for each image. """ - if user.profile.has_profile_image: - urls = _get_profile_image_urls( - _make_profile_image_name(user.username), - get_profile_image_storage(), - version=user.profile.profile_image_uploaded_at.strftime("%s"), - ) - else: + try: + if user.profile.has_profile_image: + urls = _get_profile_image_urls( + _make_profile_image_name(user.username), + get_profile_image_storage(), + version=user.profile.profile_image_uploaded_at.strftime("%s"), + ) + else: + urls = _get_default_profile_image_urls() + except UserProfile.DoesNotExist: + # when user does not have profile it raises exception, when exception + # occur we can simply get default image. urls = _get_default_profile_image_urls() if request: From 71a00bf946a35325497ddf333eb1c805f71c6cd4 Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Wed, 30 Dec 2015 10:27:09 -0500 Subject: [PATCH 08/11] Revert "Move Course Handouts to Course Resources and Tools." This reverts commit e8909e4439f22a7568d95a7672ab60d4a36f7322. --- common/lib/xmodule/xmodule/course_module.py | 4 +- .../pages/studio/settings_advanced.py | 2 +- lms/static/sass/course/_info.scss | 197 +++++++++++++----- lms/templates/courseware/info.html | 72 ++++--- 4 files changed, 184 insertions(+), 91 deletions(-) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index fd999687b6..3061f296a3 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -423,13 +423,13 @@ class CourseFields(object): scope=Scope.settings ) has_children = True - info_section_name = String( + info_sidebar_name = String( display_name=_("Course Home Sidebar Name"), help=_( "Enter the heading that you want students to see above your course handouts on the Course Home page. " "Your course handouts appear in the right panel of the page." ), - scope=Scope.settings, default=_('Course Resources and Tools')) + scope=Scope.settings, default='Course Handouts') show_timezone = Boolean( help=_( "True if timezones should be shown on dates in the courseware. " diff --git a/common/test/acceptance/pages/studio/settings_advanced.py b/common/test/acceptance/pages/studio/settings_advanced.py index 2962dbb876..10b3939c08 100644 --- a/common/test/acceptance/pages/studio/settings_advanced.py +++ b/common/test/acceptance/pages/studio/settings_advanced.py @@ -176,7 +176,7 @@ class AdvancedSettingsPage(CoursePage): 'advertised_start', 'announcement', 'display_name', - 'info_section_name', + 'info_sidebar_name', 'is_new', 'issue_badges', 'max_student_enrollments_allowed', diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index 59f0fdfbfe..dbf7cb8504 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -44,7 +44,6 @@ div.info-wrapper { background-color: $homepage-background; - border-top: none; section.updates { @extend .content; @@ -57,8 +56,7 @@ div.info-wrapper { @include text-align(left); @extend %t-strong; @extend %t-title6; - margin-bottom: 0; - @include padding(12px, 26px, 20px, 0); + margin-bottom: $baseline; font-style: normal; } @@ -95,6 +93,7 @@ div.info-wrapper { padding: $baseline; list-style-type: none; margin-bottom: lh(1.5); + background-color: $white; ol, ul { ol,ul { @@ -163,20 +162,24 @@ div.info-wrapper { } } - section.course-dates { - @include padding(32px, 30px, 20px, 30px); + section.handouts { + padding: 20px 30px; margin: 0; @extend .sidebar; background: rgba(0, 0, 0, 0); box-shadow: none; font-size: 14px; + a { + color: $link-color; + } + &:after { left: -1px; right: auto; } - .course-info-heading { + h1 { @include text-align(left); @extend %t-strong; @extend %t-title6; @@ -185,60 +188,146 @@ div.info-wrapper { font-style: normal; } - @media print { - background: transparent !important; - } - } - - section.handouts { - margin: $baseline; - border-top: ($baseline/4) solid $gray-l5; - - .course-info-heading { - @include text-align(left); - @extend %t-strong; - @extend %t-title6; - margin-top: $baseline; - margin-bottom: ($baseline/2); - padding: 0; - font-style: normal; - } - - h1 { - @include text-align(left); - @extend %t-strong; - @extend %t-title7; - margin-bottom: 0; - } - - a { - color: $link-color; - } - ul { - margin: ($baseline/2) 0; - padding: 0; - list-style: none; - - > li { - margin: ($baseline/4) 0; - } + margin-bottom: 14px; } ol { - margin: 0; - padding: 0; - display: inline-block; - list-style: none; - @include font-size(14); + margin-bottom: 14px; + li { + @include text-align(left); - > li { - margin: ($baseline/4) 0; - border-bottom: 2px solid $gray-l4; - } + a { + display: block; + padding: 0; + color: $link-color; - li:last-child { - border: none; + &:hover, + &:focus { + background: transparent; + } + } + + &.expandable, + &.collapsable { + margin: 0 16px 14px 16px; + @include transition(all .2s linear 0s); + + h4 { + color: $link-color; + font-size: 1em; + font-weight: normal; + padding-left: 30px; + } + } + + &.collapsable { + background: $white; + border-radius: 3px; + padding: 14px 0; + box-shadow: 0 0 1px 1px $shadow-l1, 0 1px 3px rgba(0, 0, 0, .25); + + h4 { + margin-bottom: 16px; + } + } + + &.multiple { + + a { + display: inline-block; + padding: 0; + + &:hover, + &:focus { + background: transparent; + } + } + } + + ul { + background: none; + margin: 0; + + li { + border-bottom: 0; + border-top: 1px solid #e6e6e6; + font-size: 0.9em; + margin: 0; + padding: 15px 30px; + + a { + display: inline-block; + padding: 0; + + &:hover, + &:focus { + background: transparent; + } + } + } + } + + div.hitarea { + background-image: url('#{$static-path}/images/treeview-default.gif') no-repeat; + display: block; + height: 100%; + margin-left: 0; + max-height: 20px; + position: absolute; + width: 100%; + + &:hover, + &:focus { + opacity: 0.6; + filter: alpha(opacity=60); + + + h4 { + @extend a:hover; + text-decoration: underline; + } + } + + &.expandable-hitarea { + background-position: -72px 0px; + } + + &.collapsable-hitarea { + background-position: -55px -23px; + } + } + + h3 { + border-bottom: 0; + box-shadow: none; + color: #888; + font-size: 1em; + margin-bottom: 0; + } + + p { + letter-spacing: 0; + margin: 0; + text-transform: none; + + a { + padding-right: 8px; + + &:before { + color: $gray-l3; + content: "•"; + display: inline-block; + padding-right: 8px; + } + + &:first-child { + &:before { + content: ""; + padding-right: 0; + } + } + } + } } } diff --git a/lms/templates/courseware/info.html b/lms/templates/courseware/info.html index dbc4b8d41b..1e19b004e6 100644 --- a/lms/templates/courseware/info.html +++ b/lms/templates/courseware/info.html @@ -38,6 +38,15 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration ToggleElementVisibility(); +<%block name="js_extra"> + + + + <%block name="bodyclass">view-in-course view-course-info ${course.css_class or ''}
    @@ -54,42 +63,37 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration % endif
    -
    - % if user.is_authenticated(): -
    - % if studio_url is not None and masquerade and masquerade.role == 'staff': - - % endif -

    ${_("Course Updates and News")}

    - ${get_course_info_section(request, masquerade_user, course, 'updates')} -
    - % if SelfPacedConfiguration.current().enable_course_home_improvements: -
    -

    ${_("Important Course Dates")}

    - ${get_course_date_summary(course, user)} -
    - % endif - % else: -
    -

    ${_("Course Updates and News")}

    - ${get_course_info_section(request, masquerade_user, course, 'guest_updates')} -
    - % endif -
    % if user.is_authenticated(): -
    -

    ${_(course.info_section_name)}

    - ${get_course_info_section(request, masquerade_user, course, 'handouts')} -
    +
    + % if studio_url is not None and masquerade and masquerade.role == 'staff': + + % endif + +

    ${_("Course Updates and News")}

    + ${get_course_info_section(request, masquerade_user, course, 'updates')} +
    +
    + % if SelfPacedConfiguration.current().enable_course_home_improvements: +

    ${_("Important Course Dates")}

    + ${get_course_date_summary(course, user)} + % endif + +

    ${_(course.info_sidebar_name)}

    + ${get_course_info_section(request, masquerade_user, course, 'handouts')} +
    % else: -
    -

    ${_("Course Handouts")}

    - ${get_course_info_section(request, course, 'guest_handouts')} -
    +
    +

    ${_("Course Updates & News")}

    + ${get_course_info_section(request, masquerade_user, course, 'guest_updates')} +
    +
    +

    ${_("Course Handouts")}

    + ${get_course_info_section(request, masquerade_user, course, 'guest_handouts')} +
    % endif
    From 77aa07544e1a74f4f6af760ddf15e13dfe6f9066 Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Mon, 4 Jan 2016 12:56:25 -0500 Subject: [PATCH 09/11] Internationalize course info sidebar default. --- common/lib/xmodule/xmodule/course_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 3061f296a3..19c9a6a0e6 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -429,7 +429,7 @@ class CourseFields(object): "Enter the heading that you want students to see above your course handouts on the Course Home page. " "Your course handouts appear in the right panel of the page." ), - scope=Scope.settings, default='Course Handouts') + scope=Scope.settings, default=_('Course Handouts')) show_timezone = Boolean( help=_( "True if timezones should be shown on dates in the courseware. " From 0c1dc272d40a00f682fb59f05be066313453015d Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Tue, 5 Jan 2016 11:11:31 -0500 Subject: [PATCH 10/11] Change "Go to Course" to "Resume Course" on course home page. ECOM-2960 --- .../courseware/tests/test_course_info.py | 11 +++++++++-- lms/djangoapps/courseware/views.py | 15 ++++++--------- lms/static/sass/course/_info.scss | 18 +++++++----------- lms/templates/courseware/info.html | 6 ++---- 4 files changed, 24 insertions(+), 26 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_course_info.py b/lms/djangoapps/courseware/tests/test_course_info.py index 79f06d1f81..9a600413bc 100644 --- a/lms/djangoapps/courseware/tests/test_course_info.py +++ b/lms/djangoapps/courseware/tests/test_course_info.py @@ -3,6 +3,7 @@ Test the course_info xblock """ import mock from nose.plugins.attrib import attr +from pyquery import PyQuery as pq from urllib import urlencode from ccx_keys.locator import CCXLocator @@ -94,10 +95,15 @@ class CourseInfoTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): self.assertEqual(response.status_code, 404) def test_last_accessed_courseware_not_shown(self): + """ + Test that the last accessed courseware link is not shown if there + is no course content. + """ SelfPacedConfiguration(enable_course_home_improvements=True).save() url = reverse('info', args=(unicode(self.course.id),)) response = self.client.get(url) - self.assertNotIn('Jump back to where you were last:', response.content) + content = pq(response.content) + self.assertEqual(content('.page-header-secondary a').length, 0) def test_last_accessed_shown(self): SelfPacedConfiguration(enable_course_home_improvements=True).save() @@ -118,7 +124,8 @@ class CourseInfoTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): self.client.get(section_url) info_url = reverse('info', args=(unicode(self.course.id),)) info_page_response = self.client.get(info_url) - self.assertIn('Jump back to where you were last:', info_page_response.content) + content = pq(info_page_response.content) + self.assertEqual(content('.page-header-secondary .last-accessed-link').attr('href'), section_url) class CourseInfoTestCaseCCX(SharedModuleStoreTestCase, LoginEnrollmentTestCase): diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 85473e46f1..11d69b9176 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -713,12 +713,9 @@ def course_info(request, course_id): } # Get the URL of the user's last position in order to display the 'where you were last' message - context['last_accessed_courseware'] = None + context['last_accessed_courseware_url'] = None if SelfPacedConfiguration.current().enable_course_home_improvements: - (section_module, section_url) = get_last_accessed_courseware(course, request) - if section_module is not None and section_url is not None: - context['last_accessed_courseware'] = section_module - context['last_accessed_url'] = section_url + context['last_accessed_courseware_url'] = get_last_accessed_courseware(course, request) now = datetime.now(UTC()) effective_start = _adjust_start_date_for_beta_testers(user, course, course_key) @@ -732,8 +729,8 @@ def course_info(request, course_id): def get_last_accessed_courseware(course, request): """ - Return a pair of the last-accessed courseware for this request's - user, and a URL for that module. + Return the URL the courseware module that this request's user last + accessed, or None if it cannot be found. """ field_data_cache = FieldDataCache.cache_for_descriptor_descendents( course.id, request.user, course, depth=2 @@ -750,8 +747,8 @@ def get_last_accessed_courseware(course, request): 'chapter': chapter_module.url_name, 'section': section_module.url_name }) - return (section_module, url) - return (None, None) + return url + return None @ensure_csrf_cookie diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index dbf7cb8504..f20bfe4baa 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -23,20 +23,16 @@ } .page-header-secondary { + @include float(right); display: inline-block; - width: flex-grid(4, 12); - margin: 0; + margin: ($baseline/2); padding: ($baseline/2) ($baseline*0.75); - border: 1px solid $blue; - background-color: $homepage-background; - @extend %t-title8; - color: $blue-d1; - @extend %cont-truncated; - vertical-align: text-bottom; + background-color: $blue; + border-radius: 2px; - .last-accessed-message { - display: inline-block; - @include margin-left($baseline*0.75); + .last-accessed-link { + @extend %t-title6; + color: $very-light-text; } } } diff --git a/lms/templates/courseware/info.html b/lms/templates/courseware/info.html index 1e19b004e6..f2346c252e 100644 --- a/lms/templates/courseware/info.html +++ b/lms/templates/courseware/info.html @@ -54,11 +54,9 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration

    ${_("Welcome to {org}'s {course_name}!").format(org=course.id.org, course_name=course.id.course) | h}

    ${course.display_name | h}

    - % if last_accessed_courseware: + % if last_accessed_courseware_url:
    - -

    ${_("Jump back to where you were last:")}

    - ${last_accessed_courseware.display_name | h} + ${_("Resume Course")}
    % endif
    From 244843d929958d9a2dd17ddfe5ef65221057c85d Mon Sep 17 00:00:00 2001 From: Peter Fogg Date: Fri, 22 Jan 2016 14:45:13 -0500 Subject: [PATCH 11/11] Track events on the course home page. Events are added for accessing the courseware, entering the verified upgrade flow, and toggling a course update. ECOM-2983 --- .../js/courseware/course_home_events.js | 16 ++++++++++++ .../courseware/toggle_element_visibility.js | 22 ++++++++++------ .../courseware/course_home_events.html | 14 ++++++++++ .../js/spec/courseware/course_home_events.js | 23 ++++++++++++++++ .../js/spec/courseware/updates_visibility.js | 26 ++++++++++++++----- lms/templates/courseware/info.html | 3 +++ 6 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 lms/static/js/courseware/course_home_events.js create mode 100644 lms/static/js/fixtures/courseware/course_home_events.html create mode 100644 lms/static/js/spec/courseware/course_home_events.js diff --git a/lms/static/js/courseware/course_home_events.js b/lms/static/js/courseware/course_home_events.js new file mode 100644 index 0000000000..d1956bee97 --- /dev/null +++ b/lms/static/js/courseware/course_home_events.js @@ -0,0 +1,16 @@ +;(function(define) { + 'use strict'; + + define(['jquery', 'logger'], function ($, Logger) { + return function () { + $('.last-accessed-link').on('click', function (event) { + Logger.log('edx.course.home.resume_course.clicked', { + url: event.currentTarget.href + }); + }); + $('.date-summary-verified-upgrade-deadline .date-summary-link').on('click', function () { + Logger.log('edx.course.home.upgrade_verified.clicked', {}); + }); + }; + }); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/courseware/toggle_element_visibility.js b/lms/static/js/courseware/toggle_element_visibility.js index b5dc438b0a..353c6a8078 100644 --- a/lms/static/js/courseware/toggle_element_visibility.js +++ b/lms/static/js/courseware/toggle_element_visibility.js @@ -1,18 +1,18 @@ ;(function (define) { 'use strict'; - define(["jquery"], - function ($) { + define(['jquery', 'logger', 'moment'], + function ($, Logger, moment) { return function () { // define variables for code legibility var toggleActionElements = $('.toggle-visibility-button'); - var updateToggleActionText = function (targetElement, actionElement) { + var updateToggleActionText = function (elementIsHidden, actionElement) { var show_text = actionElement.data('show'); var hide_text = actionElement.data('hide'); - if (targetElement.is(":visible")) { + if (elementIsHidden) { if (hide_text) { actionElement.html(actionElement.data('hide')); } else { @@ -26,15 +26,21 @@ }; $.each(toggleActionElements, function (i, elem) { - var toggleActionElement = $(elem); - var toggleTargetElement = toggleActionElement.siblings('.toggle-visibility-element'); + var toggleActionElement = $(elem), + toggleTargetElement = toggleActionElement.siblings('.toggle-visibility-element'), + elementIsHidden = toggleTargetElement.is(':visible'), + date = toggleTargetElement.siblings('.date').text(); - updateToggleActionText(toggleTargetElement, toggleActionElement); + updateToggleActionText(elementIsHidden, toggleActionElement); toggleActionElement.on('click', function (event) { event.preventDefault(); toggleTargetElement.toggleClass('hidden'); - updateToggleActionText(toggleTargetElement, toggleActionElement); + updateToggleActionText(!toggleTargetElement.hasClass('hidden'), toggleActionElement); + Logger.log('edx.course.home.course_update.toggled', { + action: elementIsHidden ? 'hide' : 'show', + publish_date: moment(date, 'MMM DD, YYYY').format() + }); }); }); }; diff --git a/lms/static/js/fixtures/courseware/course_home_events.html b/lms/static/js/fixtures/courseware/course_home_events.html new file mode 100644 index 0000000000..e115db5f41 --- /dev/null +++ b/lms/static/js/fixtures/courseware/course_home_events.html @@ -0,0 +1,14 @@ +
    +
    +

    Verification Upgrade Deadline

    +

    in 3 weeks - Feb 12, 2016

    +

    You are still eligible to upgrade to a Verified Certificate! Pursue it to highlight the knowledge and skills you gain in this course.

    + + Upgrade to Verified Certificate + +
    +
    + + diff --git a/lms/static/js/spec/courseware/course_home_events.js b/lms/static/js/spec/courseware/course_home_events.js new file mode 100644 index 0000000000..64eac2e901 --- /dev/null +++ b/lms/static/js/spec/courseware/course_home_events.js @@ -0,0 +1,23 @@ +define(['jquery', 'logger', 'js/courseware/course_home_events'], function ($, Logger, courseHomeEvents) { + 'use strict'; + + describe('Course home page eventing', function () { + beforeEach(function () { + loadFixtures('js/fixtures/courseware/course_home_events.html'); + courseHomeEvents(); + spyOn(Logger, 'log'); + }); + + it('sends an event when "Resume Course" is clicked', function () { + $('.last-accessed-link').click(); + expect(Logger.log).toHaveBeenCalledWith('edx.course.home.resume_course.clicked', { + url: "/courses/course-v1:edX+DemoX+Demo_Course/courseware/19a30717eff543078a5d94ae9d6c18a5/" + }); + }); + + it('sends an event when "Upgrade to Verified" is clicked', function () { + $('.date-summary-link').click(); + expect(Logger.log).toHaveBeenCalledWith('edx.course.home.upgrade_verified.clicked', {}); + }); + }); +}); diff --git a/lms/static/js/spec/courseware/updates_visibility.js b/lms/static/js/spec/courseware/updates_visibility.js index dc03954502..f8523f6974 100644 --- a/lms/static/js/spec/courseware/updates_visibility.js +++ b/lms/static/js/spec/courseware/updates_visibility.js @@ -1,5 +1,5 @@ -define(['jquery', 'js/courseware/toggle_element_visibility'], - function ($, ToggleElementVisibility) { +define(['jquery', 'logger', 'js/courseware/toggle_element_visibility'], + function ($, Logger, ToggleElementVisibility) { 'use strict'; describe('show/hide with mouse click', function () { @@ -9,18 +9,23 @@ define(['jquery', 'js/courseware/toggle_element_visibility'], /*jshint newcap: false */ ToggleElementVisibility(); /*jshint newcap: true */ + spyOn(Logger, 'log'); }); it('ensures update will hide on hide button click', function () { - var $shownUpdate = $('.toggle-visibility-element:not(.hidden)').first(); - $shownUpdate.siblings('.toggle-visibility-button').trigger('click'); + var $shownUpdate = $('.toggle-visibility-element:not(.hidden)').first(), + $updateButton = $shownUpdate.siblings('.toggle-visibility-button'); + $updateButton.trigger('click'); expect($shownUpdate).toHaveClass('hidden'); + expect($updateButton.text()).toEqual('Show'); }); it('ensures update will show on show button click', function () { - var $hiddenUpdate = $('.toggle-visibility-element.hidden').first(); - $hiddenUpdate.siblings('.toggle-visibility-button').trigger('click'); + var $hiddenUpdate = $('.toggle-visibility-element.hidden').first(), + $updateButton = $hiddenUpdate.siblings('.toggle-visibility-button'); + $updateButton.trigger('click'); expect($hiddenUpdate).not.toHaveClass('hidden'); + expect($updateButton.text()).toEqual('Hide'); }); it('ensures old updates will show on button click', function () { @@ -32,5 +37,14 @@ define(['jquery', 'js/courseware/toggle_element_visibility'], $('.toggle-visibility-button.show-older-updates').trigger('click'); expect($oldUpdates).not.toHaveClass('hidden'); }); + + it('sends a tracking event on hide and show', function () { + var $update = $('.toggle-visibility-element:not(.hidden)').first(); + $update.siblings('.toggle-visibility-button').trigger('click'); + expect(Logger.log).toHaveBeenCalledWith('edx.course.home.course_update.toggled', { + action: 'hide', + publish_date: '2015-12-01T00:00:00+00:00' + }); + }); }); }); diff --git a/lms/templates/courseware/info.html b/lms/templates/courseware/info.html index f2346c252e..1ee8b6b396 100644 --- a/lms/templates/courseware/info.html +++ b/lms/templates/courseware/info.html @@ -37,6 +37,9 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration <%static:require_module module_name="js/courseware/toggle_element_visibility" class_name="ToggleElementVisibility"> ToggleElementVisibility(); +<%static:require_module module_name="js/courseware/course_home_events" class_name="CourseHomeEvents"> + CourseHomeEvents(); + <%block name="js_extra">