diff --git a/common/static/sass/edx-pattern-library-shims/base/_variables.scss b/common/static/sass/edx-pattern-library-shims/base/_variables.scss index 7222357753..915d1129a3 100644 --- a/common/static/sass/edx-pattern-library-shims/base/_variables.scss +++ b/common/static/sass/edx-pattern-library-shims/base/_variables.scss @@ -191,7 +191,6 @@ $btn-border-radius: $component-border-radius !default; $btn-large-padding-vertical: spacing-vertical(small); $btn-large-padding-horizontal: spacing-horizontal(mid-large); - $btn-base-padding-vertical: spacing-vertical(x-small); $btn-base-padding-horizontal: spacing-horizontal(base); $btn-base-font-size: font-size(base); diff --git a/lms/djangoapps/courseware/tests/test_date_summary.py b/lms/djangoapps/courseware/tests/test_date_summary.py index edffea818d..2627fef09d 100644 --- a/lms/djangoapps/courseware/tests/test_date_summary.py +++ b/lms/djangoapps/courseware/tests/test_date_summary.py @@ -45,6 +45,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): days_till_upgrade_deadline=4, enroll_user=True, enrollment_mode=CourseMode.VERIFIED, + user_enrollment_mode=None, course_min_price=100, days_till_verification_deadline=14, verification_status=None, @@ -72,8 +73,11 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): ) if enroll_user: - enrollment_mode = enrollment_mode or CourseMode.DEFAULT_MODE_SLUG - CourseEnrollmentFactory.create(course_id=self.course.id, user=self.user, mode=enrollment_mode) + if user_enrollment_mode: + CourseEnrollmentFactory.create(course_id=self.course.id, user=self.user, mode=user_enrollment_mode) + else: + enrollment_mode = enrollment_mode or CourseMode.DEFAULT_MODE_SLUG + CourseEnrollmentFactory.create(course_id=self.course.id, user=self.user, mode=enrollment_mode) if days_till_verification_deadline is not None: VerificationDeadline.objects.create( @@ -274,18 +278,64 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): ) self.assertEqual(block.title, 'Course End') - ## Tests Verified Upgrade Deadline Date Block + # Tests Verified Upgrade Deadline Date Block + + def check_upgrade_banner(self, banner_expected=True, include_url_parameter=True): + """ + Helper method to check for the presence of the Upgrade Banner + """ + url = reverse('info', args=[self.course.id.to_deprecated_string()]) + if include_url_parameter: + url += '?upgrade=true' + resp = self.client.get(url) + expected_banner_text = "Give yourself an additional incentive to complete" + if banner_expected: + self.assertIn(expected_banner_text, resp.content) + else: + self.assertNotIn(expected_banner_text, resp.content) + @freeze_time('2015-01-02') def test_verified_upgrade_deadline_date(self): - self.setup_course_and_user(days_till_upgrade_deadline=1) + self.setup_course_and_user(days_till_upgrade_deadline=1, user_enrollment_mode=CourseMode.AUDIT) + self.client.login(username='mrrobot', password='test') block = VerifiedUpgradeDeadlineDate(self.course, self.user) self.assertEqual(block.date, datetime.now(utc) + timedelta(days=1)) + self.assertTrue(block.is_enabled) self.assertEqual(block.link, reverse('verify_student_upgrade_and_verify', args=(self.course.id,))) + self.check_upgrade_banner() def test_without_upgrade_deadline(self): self.setup_course_and_user(enrollment_mode=None) + self.client.login(username='mrrobot', password='test') block = VerifiedUpgradeDeadlineDate(self.course, self.user) + self.assertFalse(block.is_enabled) self.assertIsNone(block.date) + self.check_upgrade_banner(banner_expected=False) + + @freeze_time('2015-01-02') + def test_verified_upgrade_banner_not_present_past_deadline(self): + self.setup_course_and_user(days_till_upgrade_deadline=-1, user_enrollment_mode=CourseMode.AUDIT) + self.client.login(username='mrrobot', password='test') + block = VerifiedUpgradeDeadlineDate(self.course, self.user) + self.assertFalse(block.is_enabled) + self.check_upgrade_banner(banner_expected=False) + + @freeze_time('2015-01-02') + def test_verified_upgrade_banner_cookie(self): + self.setup_course_and_user(days_till_upgrade_deadline=1, user_enrollment_mode=CourseMode.AUDIT) + self.client.login(username='mrrobot', password='test') + + # No URL parameter or cookie present, notification should not be shown. + self.check_upgrade_banner(include_url_parameter=False, banner_expected=False) + + # Now pass URL parameter-- notification should be shown. + self.check_upgrade_banner(include_url_parameter=True) + + # A cookie should be set in the previous call, so it is no longer necessary to pass + # the URL parameter in order to see the notification. + self.check_upgrade_banner(include_url_parameter=False) + + # Unfortunately (according to django doc), it is not possible to test expiration of the cookie. def test_ecommerce_checkout_redirect(self): """Verify the block link redirects to ecommerce checkout if it's enabled.""" diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index 5e235cac68..e2d448f462 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -65,6 +65,7 @@ from courseware.courses import ( sort_by_start_date, UserNotEnrolled ) +from courseware.date_summary import VerifiedUpgradeDeadlineDate from courseware.masquerade import setup_masquerade from courseware.model_data import FieldDataCache from courseware.models import StudentModule, BaseStudentModuleHistory @@ -343,6 +344,22 @@ def course_info(request, course_id): if settings.FEATURES.get('ENABLE_MKTG_SITE'): url_to_enroll = marketing_link('COURSES') + store_upgrade_cookie = False + upgrade_cookie_name = 'show_upgrade_notification' + upgrade_link = None + if request.user.is_authenticated(): + show_upgrade_notification = False + if request.GET.get('upgrade', 'false') == 'true': + store_upgrade_cookie = True + show_upgrade_notification = True + elif upgrade_cookie_name in request.COOKIES and bool(request.COOKIES[upgrade_cookie_name]): + show_upgrade_notification = True + + if show_upgrade_notification: + upgrade_data = VerifiedUpgradeDeadlineDate(course, user) + if upgrade_data.is_enabled: + upgrade_link = upgrade_data.link + context = { 'request': request, 'masquerade_user': user, @@ -354,6 +371,7 @@ def course_info(request, course_id): 'studio_url': studio_url, 'show_enroll_banner': show_enroll_banner, 'url_to_enroll': url_to_enroll, + 'upgrade_link': upgrade_link } # Get the URL of the user's last position in order to display the 'where you were last' message @@ -371,7 +389,17 @@ def course_info(request, course_id): if CourseEnrollment.is_enrolled(request.user, course.id): inject_coursetalk_keys_into_context(context, course_key) - return render_to_response('courseware/info.html', context) + response = render_to_response('courseware/info.html', context) + if store_upgrade_cookie: + response.set_cookie( + upgrade_cookie_name, + True, + max_age=10 * 24 * 60 * 60, # set for 10 days + domain=settings.SESSION_COOKIE_DOMAIN, + httponly=True # no use case for accessing from JavaScript + ) + + return response def get_last_accessed_courseware(course, request, user): diff --git a/lms/static/images/edx-verified-mini-cert.png b/lms/static/images/edx-verified-mini-cert.png new file mode 100644 index 0000000000..35b9b9f1dc Binary files /dev/null and b/lms/static/images/edx-verified-mini-cert.png differ diff --git a/lms/static/js/courseware/course_home_events.js b/lms/static/js/courseware/course_home_events.js index 28f49d06d6..9b9da6ca73 100644 --- a/lms/static/js/courseware/course_home_events.js +++ b/lms/static/js/courseware/course_home_events.js @@ -9,7 +9,13 @@ }); }); $('.date-summary-verified-upgrade-deadline .date-summary-link').on('click', function() { - Logger.log('edx.course.home.upgrade_verified.clicked', {}); + Logger.log('edx.course.home.upgrade_verified.clicked', {location: 'sidebar'}); + }); + $('.upgrade-banner-button').on('click', function() { + Logger.log('edx.course.home.upgrade_verified.clicked', {location: 'notification'}); + }); + $('.view-verified-info').on('click', function() { + Logger.log('edx.course.home.learn_about_verified.clicked', {location: 'notification'}); }); }; }); diff --git a/lms/static/js/fixtures/courseware/course_home_events.html b/lms/static/js/fixtures/courseware/course_home_events.html index e115db5f41..a86ab48b53 100644 --- a/lms/static/js/fixtures/courseware/course_home_events.html +++ b/lms/static/js/fixtures/courseware/course_home_events.html @@ -1,3 +1,21 @@ +
+
+
+
+ +
+
+

Give yourself an additional incentive to complete

+

Earn a verified certificate + Learn More +

+
+ +
+
+

Verification Upgrade Deadline

diff --git a/lms/static/js/spec/courseware/course_home_events_spec.js b/lms/static/js/spec/courseware/course_home_events_spec.js index 749e0c13aa..c7eee6ba13 100644 --- a/lms/static/js/spec/courseware/course_home_events_spec.js +++ b/lms/static/js/spec/courseware/course_home_events_spec.js @@ -17,9 +17,23 @@ define(['jquery', 'logger', 'js/courseware/course_home_events'], function($, Log }); }); - it('sends an event when "Upgrade to Verified" is clicked', function() { + it('sends an event when "Upgrade to Verified" is clicked from the sidebar', function() { $('.date-summary-link').click(); - expect(Logger.log).toHaveBeenCalledWith('edx.course.home.upgrade_verified.clicked', {}); + expect(Logger.log).toHaveBeenCalledWith('edx.course.home.upgrade_verified.clicked', {location: 'sidebar'}); + }); + + it('sends an event when "Upgrade Now" is clicked from the upsell notification', function() { + $('.upgrade-banner-button').click(); + expect(Logger.log).toHaveBeenCalledWith( + 'edx.course.home.upgrade_verified.clicked', {location: 'notification'} + ); + }); + + it('sends an event when "Learn More" is clicked from the upsell notification', function() { + $('.view-verified-info').click(); + expect(Logger.log).toHaveBeenCalledWith( + 'edx.course.home.learn_about_verified.clicked', {location: 'notification'} + ); }); }); }); diff --git a/lms/static/sass/course/_info.scss b/lms/static/sass/course/_info.scss index 2d1a57b24f..fac2c9ed18 100644 --- a/lms/static/sass/course/_info.scss +++ b/lms/static/sass/course/_info.scss @@ -1,3 +1,20 @@ +// Upgrade button +$btn-upgrade-border-color: $uxpl-green-base !default; +$btn-upgrade-background: $uxpl-green-base !default; +$btn-upgrade-color: #fcfcfc !default; +$btn-upgrade-focus-color: $btn-upgrade-color !default; +$btn-upgrade-focus-border-color: rgb(0, 155, 0) !default; +$btn-upgrade-focus-background: rgb(0, 155, 0) !default; +$btn-upgrade-active-border-color: $uxpl-green-base !default; +$btn-upgrade-active-background: $uxpl-green-base !default; + +//// Notifications +// Upgrade + +$notification-highlight-border-color: $uxpl-green-base !default; +$lms-border-color: $uxpl-gray-background !default; +$notification-background: rgb(255, 255, 255) !default + .home { @include clearfix(); max-width: 1140px; @@ -56,6 +73,110 @@ div.info-wrapper { font-style: normal; } + div.upgrade-banner { + // This banner uses the Pattern Library's defined variables + @include border-left(0px); + + border: 1px solid $lms-border-color; + width: 100%; + display: table; + + .notification-color-border { + width: 6px; //Value defined by UX team + min-height: 100%; + margin: 0; + display: table-cell; + background: $notification-highlight-border-color; + } + + .notification-content { + display: inline-flex; + align-items: center; + align-content: flex-start; + flex-flow: row wrap; + background: $notification-background; + width: 100%; + padding: $baseline/2 0; + margin-bottom: 0; + justify-content: space-between; + + .upgrade-icon { + margin: 0; + padding: $baseline/2 $baseline; + flex-flow: row nowrap; + align-items: center; + // flex: grow, shrink, base + // The 7 was the value that allowed the icon image to grow to the UX + // desired size. + flex: 7 1 50px; + // The following dimensions were added so that the + // icon will adjust as the notification is adjusted + // but will not be smaller or larger than UX requirements. + min-height: 50px; + min-width: 80px; + max-height: 90px; + max-width: 130px; + + img { + min-height: 50px; + min-width: 80px; + } + } + + .upgrade-msg { + flex: 5 1 60%; //This percentage was required to get the text + // in the message to wrap when collapsed. + flex-direction: column; + margin: 0; + padding: $baseline/2 0; + .msg-title { + font-weight: font-weight(semi-bold); + font-size: font-size(large); + line-height: $base-line-height; + } + .view-verified-info { + margin-top: $baseline/4; + font-weight: font-weight(normal); + font-size: font-size(base); + } + + a:link, a:hover, a:visited, a:active { + text-decoration: underline !important; + } + } + + .upgrade-banner-button { + @include margin(0, 0, 0, auto); + padding: $baseline/2 $baseline; + } + + .btn-upgrade { + @extend %btn-shims; + + border-color: $btn-upgrade-border-color; + background: $btn-upgrade-background; + color: $btn-upgrade-color; + // STATE: hover and focus + &:hover, + &.is-hovered, + &:focus, + &.is-focused { + border-color: $btn-upgrade-focus-border-color; + background-color: $btn-upgrade-focus-background; + color: $btn-upgrade-focus-color; + } + + // STATE: is disabled + &:disabled, + &.is-disabled { + border-color: $btn-disabled-border-color; + background: $btn-brand-disabled-background; + color: $btn-upgrade-color; + } + } + } + } + > p { margin-bottom: lh(); } diff --git a/lms/templates/courseware/info.html b/lms/templates/courseware/info.html index 7e9b874341..1891c4c5a2 100644 --- a/lms/templates/courseware/info.html +++ b/lms/templates/courseware/info.html @@ -81,6 +81,26 @@ from openedx.core.djangolib.markup import HTML, Text
% endif + % if upgrade_link: +
+
+
+
+ +
+
+

${_("Give yourself an additional incentive to complete")}

+

${_("Earn a verified certificate.")} + ${_("Learn More")} +

+
+ +
+
+ % endif +

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

${HTML(get_course_info_section(request, masquerade_user, course, 'updates'))}