From 0a6029f7a7434a47c24d15ac6f5007f449f5e063 Mon Sep 17 00:00:00 2001
From: Clinton Blackburn <cblackburn@edx.org>
Date: Thu, 24 Aug 2017 17:32:19 -0400
Subject: [PATCH] Added verified upgrade hero to course run homepage

Audit learners are now shown a prompt to upgrade to the verified track
of the course run. This message goes away after the learner upgrades.
---
 common/djangoapps/student/models.py                                                            |  11 +++++++----
 lms/static/sass/_build-lms-v2.scss                                                             |   2 ++
 lms/static/sass/features/_course-upgrade-message.scss                                          | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 openedx/features/course_experience/__init__.py                                                 |   2 ++
 openedx/features/course_experience/static/course_experience/fixtures/course-home-fragment.html |  24 ++++++++++++++++++++++++
 openedx/features/course_experience/static/course_experience/js/CourseHome.js                   |  97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 openedx/features/course_experience/static/course_experience/js/spec/CourseHome_spec.js         |  76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
 openedx/features/course_experience/templates/course_experience/course-home-fragment.html       |  32 ++++++++++++++++++++++++++++++++
 openedx/features/course_experience/tests/views/test_course_home.py                             | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
 openedx/features/course_experience/views/course_home.py                                        |  24 ++++++++++++++++++++++--
 10 files changed, 467 insertions(+), 29 deletions(-)
 create mode 100644 lms/static/sass/features/_course-upgrade-message.scss

diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py
index 74a9748..dd5372d 100644
--- a/common/djangoapps/student/models.py
+++ b/common/djangoapps/student/models.py
@@ -33,6 +33,7 @@ from django.db.models import Count
 from django.db.models.signals import post_save, pre_save
 from django.dispatch import Signal, receiver
 from django.utils import timezone
+from django.utils.functional import cached_property
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_noop
 from django_countries.fields import CountryField
@@ -1688,6 +1689,10 @@ class CourseEnrollment(models.Model):
                 self._course_overview = None
         return self._course_overview
 
+    @cached_property
+    def verified_mode(self):
+        return CourseMode.verified_mode_for_course(self.course_id)
+
     @property
     def upgrade_deadline(self):
         """
@@ -1723,11 +1728,9 @@ class CourseEnrollment(models.Model):
             pass
 
         try:
-            verified_mode = CourseMode.verified_mode_for_course(self.course_id)
-
-            if verified_mode:
+            if self.verified_mode:
                 log.debug('Schedules: Defaulting to verified mode expiration date-time for %s.', self.course_id)
-                return verified_mode.expiration_datetime
+                return self.verified_mode.expiration_datetime
             else:
                 log.debug('Schedules: No verified mode located for %s.', self.course_id)
         except CourseMode.DoesNotExist:
diff --git a/lms/static/sass/_build-lms-v2.scss b/lms/static/sass/_build-lms-v2.scss
index 9ea180b..65ac354 100644
--- a/lms/static/sass/_build-lms-v2.scss
+++ b/lms/static/sass/_build-lms-v2.scss
@@ -21,6 +21,7 @@
 
 // Elements
 @import 'notifications';
+@import 'elements/controls';
 @import 'elements-v2/pagination';
 
 // Features
@@ -28,6 +29,7 @@
 @import 'features/course-experience';
 @import 'features/course-search';
 @import 'features/course-sock';
+@import 'features/course-upgrade-message';
 
 // Views
 @import "views/program-marketing-page";
diff --git a/lms/static/sass/features/_course-upgrade-message.scss b/lms/static/sass/features/_course-upgrade-message.scss
new file mode 100644
index 0000000..a9a3f06
--- /dev/null
+++ b/lms/static/sass/features/_course-upgrade-message.scss
@@ -0,0 +1,126 @@
+/*
+  NOTE: If you make significant changes to the design, remember to update the Segment event properties and change
+  the creative property. This will allow us to better track individual performance of each style of the message.
+  Search for the courseware_verified_certificate_upsell promotion ID.
+ */
+$upgrade-message-background-color: $blue-d1;
+
+// Expanded upgrade message
+.vc-message {
+  background: $blue-d1;
+  color: $white;
+  padding: $baseline;
+  position: relative;
+  margin: 0 0 $baseline;
+
+  // CSS animation for smooth height transition
+  -webkit-transition: all 0.2s ease-in-out;
+  transition: all 0.2s ease-in-out;
+
+  &:after {
+    content: "";
+    display: table;
+    clear: both;
+  }
+
+  // Message copy
+  .vc-title {
+    font-size: 1.25rem;
+    font-weight: 700;
+    margin-bottom: 1rem;
+    @include float(left);
+  }
+
+  .vc-selling-points {
+    @include clear(left);
+    @include padding-left(0);
+    font-size: 0.825rem;
+    margin: 1rem 0;
+    display: table;
+
+    > .vc-selling-point {
+      list-style: none;
+      display: table-row;
+
+      &:before {
+        content: "\2022";
+        display: table-cell;
+        @include padding-right($baseline/2);
+      }
+
+      &:after {
+        content: "";
+        display: table-row;
+        height: 0.25rem;
+      }
+    }
+  }
+  img {
+    max-width: 100%;
+  }
+
+  // Show/hide button
+  .vc-toggle {
+    @include float(right);
+    color: $white;
+  }
+
+  // Upgrade Button
+  .btn-upgrade {
+    @extend %btn-primary-green;
+    background: $uxpl-green-base;
+  }
+
+  // Cert image
+  .vc-hero {
+    @include float(right);
+    @include padding-left(1rem);
+    clear: both;
+    width: 35%;
+
+    img {
+      max-width: 100%;
+    }
+  }
+}
+
+// Collapsed upgrade message
+.vc-message.polite {
+  padding-top: $baseline/2;
+  padding-bottom: $baseline/2;
+  min-height: 46px;
+  display: flex;
+  flex-flow: row wrap;
+  align-items: center;
+
+  .vc-title {
+    margin: 0;
+    @include margin-right(auto);
+  }
+
+  .vc-cta {
+    @include margin-right(1rem);
+  }
+
+  .vc-toggle {
+    order: 99;
+  }
+
+  .vc-fade:not(.vc-polite-only) {
+    display: none;
+  }
+}
+
+@media (max-width: $bp-screen-md) {
+  .vc-message.polite .vc-title {
+    clear: both;
+    width: 100%;
+    margin-bottom: 1rem;
+  }
+}
+
+@media (max-width: $bp-screen-sm) {
+  .vc-message .vc-hero {
+    display: none !important;
+  }
+}
diff --git a/openedx/features/course_experience/__init__.py b/openedx/features/course_experience/__init__.py
index c079b2c..31f994e 100644
--- a/openedx/features/course_experience/__init__.py
+++ b/openedx/features/course_experience/__init__.py
@@ -25,6 +25,8 @@ COURSE_PRE_START_ACCESS_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'pre_star
 # Waffle flag to enable a review page link from the unified home page
 SHOW_REVIEWS_TOOL_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'show_reviews_tool')
 
+SHOW_UPGRADE_MSG_ON_COURSE_HOME = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'show_upgrade_msg_on_course_home')
+
 # Waffle flag to switch between the 'welcome message' and 'latest update' on the course home page.
 # Important Admin Note: This is meant to be configured using waffle_utils course
 #   override only.  Either do not create the actual waffle flag, or be sure to unset the
diff --git a/openedx/features/course_experience/static/course_experience/fixtures/course-home-fragment.html b/openedx/features/course_experience/static/course_experience/fixtures/course-home-fragment.html
index d3007af..134c535 100644
--- a/openedx/features/course_experience/static/course_experience/fixtures/course-home-fragment.html
+++ b/openedx/features/course_experience/static/course_experience/fixtures/course-home-fragment.html
@@ -29,6 +29,30 @@
     <div class="page-content">
         <div class="layout layout-1t2t">
             <main class="layout-col layout-col-b">
+                <div class="section">
+                    <div class="vc-message tex2jax_ignore" role="group" aria-labelledby="vc-title" tabindex="-1" style="display: none;">
+                        <h3 class="vc-title vc-fade vc-polite-only">Pursue a verified certificate</h3>
+                        <button class="vc-toggle vc-fade vc-polite-only btn-link" type="button" aria-controls="moreinfo"
+                                aria-expanded="true" aria-label="Show/Hide">Show less
+                        </button>
+
+                        <div class="vc-hero vc-fade">
+                            <img src="img/sample-certificate.png"
+                                 alt="Sample verified certificate with your name, the course title, the logo of the institution and the signatures of the instructors for this course."/>
+                        </div>
+
+                        <ul class="vc-selling-points vc-fade">
+                            <li class="vc-selling-point">Official proof of completion</li>
+                            <li class="vc-selling-point">Easily shareable certificate</li>
+                            <li class="vc-selling-point">Proven motivator to complete the course</li>
+                            <li class="vc-selling-point">Certificate purchases help us continue to offer free courses</li>
+                        </ul>
+
+                        <div class="vc-cta vc-fade vc-polite-only">
+                            <a class="btn btn-upgrade" href="#">Upgrade ($100)</a>
+                        </div>
+                    </div>
+                </div>
                 <div class="section section-dates">
                     <div class="welcome-message">
                         <div class="dismiss-message">
diff --git a/openedx/features/course_experience/static/course_experience/js/CourseHome.js b/openedx/features/course_experience/static/course_experience/js/CourseHome.js
index b6d3b81..523c4c5 100644
--- a/openedx/features/course_experience/static/course_experience/js/CourseHome.js
+++ b/openedx/features/course_experience/static/course_experience/js/CourseHome.js
@@ -1,7 +1,10 @@
-/* globals Logger */
+/* globals gettext, Logger */
 
 export class CourseHome {  // eslint-disable-line import/prefer-default-export
   constructor(options) {
+    this.courseRunKey = options.courseRunKey;
+    this.msgStateStorageKey = `course_experience.upgrade_msg.${this.courseRunKey}.collapsed`;
+
     // Logging for 'Resume Course' or 'Start Course' button click
     const $resumeCourseLink = $(options.resumeCourseLink);
     $resumeCourseLink.on('click', (event) => {
@@ -26,5 +29,97 @@ export class CourseHome {  // eslint-disable-line import/prefer-default-export
         },
       );
     });
+
+    $(document).ready(() => {
+      this.configureUpgradeMessage();
+    });
+  }
+
+  static fireSegmentEvent(event, properties) {
+    /* istanbul ignore next */
+    if (!window.analytics) {
+      return;
+    }
+
+    window.analytics.track(event, properties);
+  }
+
+  /**
+   * Persists the collapsed state of the upgrade message. If the message is collapsed,
+   * this information is persisted to local storage. Expanding the message *removes* the
+   * key from local storage.
+   */
+  persistUpgradeMessageState(collapsed) {
+    if (window.localStorage) {
+      if (collapsed) {
+        window.localStorage.setItem(this.msgStateStorageKey, true);
+      } else {
+        window.localStorage.removeItem(this.msgStateStorageKey);
+      }
+    }
+  }
+
+  configureUpgradeMessage() {
+    const $vcMessage = $('.vc-message');
+    const $vcDismissToggle = $('.vc-toggle', $vcMessage);
+    const logEventProperties = { courseRunKey: this.courseRunKey };
+    const promotionEventProperties = {
+      promotion_id: 'courseware_verified_certificate_upsell',
+      creative: 'original_hero',
+      name: 'In-Course Verification Prompt',
+      position: 'hero',
+    };
+
+    CourseHome.fireSegmentEvent('Promotion Viewed', promotionEventProperties);
+    Logger.log('edx.course.upgrade.hero.displayed', logEventProperties);
+
+    // Get height of container and button
+    let vcHeight = $vcMessage.outerHeight();
+
+    // Update based on window
+    window.onresize = () => {
+      if (!$vcMessage.hasClass('polite')) {
+        vcHeight = $vcMessage.outerHeight();
+      }
+    };
+
+    function collapseMessage(duration = 400) {
+      $('.vc-fade').fadeOut(duration, () => {
+        $vcDismissToggle.text(gettext('Show more')).attr('aria-expanded', false);
+        $('.vc-polite-only').fadeIn(duration);
+        $vcMessage.height('auto').addClass('polite');
+      });
+    }
+
+    // Use the previously-persisted state to determine the initial display state of the message.
+    if (window.localStorage && window.localStorage.getItem(this.msgStateStorageKey)) {
+      collapseMessage(0);
+    }
+    $vcMessage.show();
+
+    $vcDismissToggle.click(() => {
+      if ($vcMessage.hasClass('polite')) {
+        // Expand message
+        Logger.log('edx.course.upgrade.hero.expanded', logEventProperties);
+        this.persistUpgradeMessageState(false);
+
+        $('.vc-fade').fadeOut(400);
+        $vcMessage.animate({ height: vcHeight }, 400, () => {
+          $vcMessage.height('auto').removeClass('polite');
+          $vcDismissToggle.text(gettext('Show less')).attr('aria-expanded', true);
+          $('.vc-fade').fadeIn(400);
+        });
+      } else {
+        // Collapse message
+        Logger.log('edx.course.upgrade.hero.collapsed', logEventProperties);
+        this.persistUpgradeMessageState(true);
+        collapseMessage();
+      }
+    });
+
+    $('.btn-upgrade', $vcMessage).click(() => {
+      CourseHome.fireSegmentEvent('Promotion Clicked', promotionEventProperties);
+      Logger.log('edx.course.upgrade.hero.clicked', logEventProperties);
+    });
   }
 }
diff --git a/openedx/features/course_experience/static/course_experience/js/spec/CourseHome_spec.js b/openedx/features/course_experience/static/course_experience/js/spec/CourseHome_spec.js
index be48d90..c31eb47 100644
--- a/openedx/features/course_experience/static/course_experience/js/spec/CourseHome_spec.js
+++ b/openedx/features/course_experience/static/course_experience/js/spec/CourseHome_spec.js
@@ -3,18 +3,21 @@
 import { CourseHome } from '../CourseHome';
 
 describe('Course Home factory', () => {
-  describe('Ensure course tool click logging', () => {
-    let home; // eslint-disable-line no-unused-vars
+  let home;
+  const runKey = 'course-v1:edX+DemoX+Demo_Course';
+  window.analytics = jasmine.createSpyObj('analytics', ['page', 'track', 'trackLink']);
 
-    beforeEach(() => {
-      loadFixtures('course_experience/fixtures/course-home-fragment.html');
-      home = new CourseHome({
-        resumeCourseLink: '.action-resume-course',
-        courseToolLink: '.course-tool-link',
-      });
-      spyOn(Logger, 'log');
+  beforeEach(() => {
+    loadFixtures('course_experience/fixtures/course-home-fragment.html');
+    spyOn(Logger, 'log');
+    home = new CourseHome({ // eslint-disable-line no-unused-vars
+      courseRunKey: runKey,
+      resumeCourseLink: '.action-resume-course',
+      courseToolLink: '.course-tool-link',
     });
+  });
 
+  describe('Ensure course tool click logging', () => {
     it('sends an event when resume or start course is clicked', () => {
       $('.action-resume-course').click();
       expect(Logger.log).toHaveBeenCalledWith(
@@ -22,7 +25,7 @@ describe('Course Home factory', () => {
         {
           event_type: 'start',
           url: `http://${window.location.host}/courses/course-v1:edX+DemoX+Demo_Course/courseware` +
-            '/19a30717eff543078a5d94ae9d6c18a5/',
+          '/19a30717eff543078a5d94ae9d6c18a5/',
         },
       );
     });
@@ -43,4 +46,57 @@ describe('Course Home factory', () => {
       }
     });
   });
+
+  describe('Upgrade message events', () => {
+    const segmentEventProperties = {
+      promotion_id: 'courseware_verified_certificate_upsell',
+      creative: 'original_hero',
+      name: 'In-Course Verification Prompt',
+      position: 'hero',
+    };
+
+    it('should send events to Segment and edX on initial load', () => {
+      expect(window.analytics.track).toHaveBeenCalledWith('Promotion Viewed', segmentEventProperties);
+      expect(Logger.log).toHaveBeenCalledWith('edx.course.upgrade.hero.displayed', { courseRunKey: runKey });
+    });
+
+    it('should send events to Segment and edX after clicking the upgrade button ', () => {
+      $('.vc-message .btn-upgrade').click();
+      expect(window.analytics.track).toHaveBeenCalledWith('Promotion Viewed', segmentEventProperties);
+      expect(Logger.log).toHaveBeenCalledWith('edx.course.upgrade.hero.clicked', { courseRunKey: runKey });
+    });
+  });
+
+  describe('upgrade message display toggle', () => {
+    let $message;
+    let $toggle;
+
+    beforeEach(() => {
+      $.fx.off = true;
+
+      $message = $('.vc-message');
+      $toggle = $('.vc-toggle', $message);
+      expect($message.length).toEqual(1);
+      expect($toggle.length).toEqual(1);
+    });
+
+    it('hides/shows the message and writes/removes a key from local storage', () => {
+      // NOTE (CCB): Ideally this should be two tests--one for collapse, another for expansion.
+      // After a couple hours I have been unable to make these two tests pass, probably due to
+      // issues with the initial state of local storage.
+      expect($message.is(':visible')).toBeTruthy();
+      expect($message.hasClass('polite')).toBeFalsy();
+      expect($toggle.text().trim()).toEqual('Show less');
+
+      $toggle.click();
+      expect($message.hasClass('polite')).toBeTruthy();
+      expect($toggle.text().trim()).toEqual('Show more');
+      expect(window.localStorage.getItem(home.msgStateStorageKey)).toEqual('true');
+
+      $toggle.click();
+      expect($message.hasClass('polite')).toBeFalsy();
+      expect($toggle.text().trim()).toEqual('Show less');
+      expect(window.localStorage.getItem(home.msgStateStorageKey)).toBeNull();
+    });
+  });
 });
diff --git a/openedx/features/course_experience/templates/course_experience/course-home-fragment.html b/openedx/features/course_experience/templates/course_experience/course-home-fragment.html
index 058d094..7216dae 100644
--- a/openedx/features/course_experience/templates/course_experience/course-home-fragment.html
+++ b/openedx/features/course_experience/templates/course_experience/course-home-fragment.html
@@ -57,6 +57,37 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV
     <div class="page-content">
         <div class="layout layout-1t2t">
             <main class="layout-col layout-col-b">
+
+                % if upgrade_url and upgrade_price:
+                    <div class="section">
+                        <div class="vc-message tex2jax_ignore" role="group" aria-labelledby="vc-title" tabindex="-1" style="display: none;">
+
+                            <h3 class="vc-title vc-fade vc-polite-only">Pursue a verified certificate</h3>
+
+                            <button class="vc-toggle vc-fade vc-polite-only btn-link" type="button" aria-controls="moreinfo"
+                                    aria-expanded="true" aria-label="${_("Show/Hide")}">
+                                ${_("Show less")}
+                            </button>
+
+                            <div class="vc-hero vc-fade">
+                                <img src="${static.url('course_experience/images/verified-cert.png')}"
+                                     alt="${_("Sample verified certificate with your name, the course title, the logo of the institution and the signatures of the instructors for this course.")}"/>
+                            </div>
+
+                            <ul class="vc-selling-points vc-fade">
+                                <li class="vc-selling-point">${_("Official proof of completion")}</li>
+                                <li class="vc-selling-point">${_("Easily shareable certificate")}</li>
+                                <li class="vc-selling-point">${_("Proven motivator to complete the course")}</li>
+                                <li class="vc-selling-point">${_("Certificate purchases help us continue to offer free courses")}</li>
+                            </ul>
+
+                            <div class="vc-cta vc-fade vc-polite-only">
+                                <a class="btn-upgrade" href="${ upgrade_url }">${_("Upgrade ({price})").format(price='$' + str(upgrade_price))}</a>
+                            </div>
+                        </div>
+                    </div>
+                % endif
+
                 % if course_home_message_fragment:
                     ${HTML(course_home_message_fragment.body_html())}
                 % endif
@@ -109,6 +140,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV
 
 <%static:webpack entry="CourseHome">
     new CourseHome({
+        courseRunKey: "${course_key | n, js_escaped_string}",
         resumeCourseLink: ".action-resume-course",
         courseToolLink: ".course-tool-link",
     });
diff --git a/openedx/features/course_experience/tests/views/test_course_home.py b/openedx/features/course_experience/tests/views/test_course_home.py
index 141d138..1a65f28 100644
--- a/openedx/features/course_experience/tests/views/test_course_home.py
+++ b/openedx/features/course_experience/tests/views/test_course_home.py
@@ -3,28 +3,36 @@
 Tests for the course home page.
 """
 from datetime import datetime, timedelta
+
 import ddt
 import mock
-from pytz import UTC
-from waffle.testutils import override_flag
-
-from courseware.tests.factories import StaffFactory
 from django.conf import settings
 from django.core.urlresolvers import reverse
 from django.http import QueryDict
 from django.utils.http import urlquote_plus
+from pytz import UTC
+from waffle.models import Flag
+from waffle.testutils import override_flag
+
+from commerce.models import CommerceConfiguration
+from commerce.utils import EcommerceService
+from course_modes.models import CourseMode
+from courseware.tests.factories import StaffFactory
 from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES, override_waffle_flag
-from openedx.features.course_experience import SHOW_REVIEWS_TOOL_FLAG, UNIFIED_COURSE_TAB_FLAG
+from openedx.features.course_experience import (
+    SHOW_REVIEWS_TOOL_FLAG,
+    SHOW_UPGRADE_MSG_ON_COURSE_HOME,
+    UNIFIED_COURSE_TAB_FLAG
+)
 from student.models import CourseEnrollment
 from student.tests.factories import UserFactory
 from util.date_utils import strftime_localized
 from xmodule.modulestore import ModuleStoreEnum
-from xmodule.modulestore.tests.django_utils import CourseUserType, SharedModuleStoreTestCase
+from xmodule.modulestore.tests.django_utils import CourseUserType, ModuleStoreTestCase, SharedModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls
-
-from ... import COURSE_PRE_START_ACCESS_FLAG
 from .helpers import add_course_mode
 from .test_course_updates import create_course_update
+from ... import COURSE_PRE_START_ACCESS_FLAG
 
 TEST_PASSWORD = 'test'
 TEST_CHAPTER_NAME = 'Test Chapter'
@@ -68,6 +76,7 @@ class CourseHomePageTestCase(SharedModuleStoreTestCase):
     """
     Base class for testing the course home page.
     """
+
     @classmethod
     def setUpClass(cls):
         """
@@ -113,9 +122,6 @@ class CourseHomePageTestCase(SharedModuleStoreTestCase):
 
 class TestCourseHomePage(CourseHomePageTestCase):
     def setUp(self):
-        """
-        Set up for the tests.
-        """
         super(TestCourseHomePage, self).setUp()
         self.client.login(username=self.user.username, password=TEST_PASSWORD)
 
@@ -160,7 +166,7 @@ class TestCourseHomePage(CourseHomePageTestCase):
         course_home_url(self.course)
 
         # Fetch the view and verify the query counts
-        with self.assertNumQueries(41, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
+        with self.assertNumQueries(42, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
             with check_mongo_calls(4):
                 url = course_home_url(self.course)
                 self.client.get(url)
@@ -374,3 +380,75 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
         response = self.client.get(url)
         self.assertContains(response, TEST_COURSE_HOME_MESSAGE)
         self.assertContains(response, TEST_COURSE_HOME_MESSAGE_PRE_START)
+
+
+class CourseHomeFragmentViewTests(ModuleStoreTestCase):
+    CREATE_USER = False
+
+    def setUp(self):
+        super(CourseHomeFragmentViewTests, self).setUp()
+        CommerceConfiguration.objects.create(checkout_on_ecommerce_service=True)
+
+        end = datetime.now(UTC) + timedelta(days=30)
+        self.course = CourseFactory(
+            start=datetime.now(UTC) - timedelta(days=30),
+            end=end,
+        )
+        self.url = course_home_url(self.course)
+
+        CourseMode.objects.create(course_id=self.course.id, mode_slug=CourseMode.AUDIT)
+        self.verified_mode = CourseMode.objects.create(
+            course_id=self.course.id,
+            mode_slug=CourseMode.VERIFIED,
+            min_price=100,
+            expiration_datetime=end,
+            sku='test'
+        )
+
+        self.user = UserFactory()
+        self.client.login(username=self.user.username, password=TEST_PASSWORD)
+
+        name = SHOW_UPGRADE_MSG_ON_COURSE_HOME.waffle_namespace._namespaced_name(
+            SHOW_UPGRADE_MSG_ON_COURSE_HOME.flag_name)
+        self.flag, __ = Flag.objects.update_or_create(name=name, defaults={'everyone': True})
+
+    def assert_upgrade_message_not_displayed(self):
+        response = self.client.get(self.url)
+        self.assertNotIn('vc-message', response.content)
+
+    def assert_upgrade_message_displayed(self):
+        response = self.client.get(self.url)
+        self.assertIn('vc-message', response.content)
+        url = EcommerceService().get_checkout_page_url(self.verified_mode.sku)
+        expected = '<a class="btn-upgrade" href="{url}">Upgrade (${price})</a>'.format(
+            url=url,
+            price=self.verified_mode.min_price
+        )
+        self.assertIn(expected, response.content)
+
+    def test_no_upgrade_message_if_logged_out(self):
+        self.client.logout()
+        self.assert_upgrade_message_not_displayed()
+
+    def test_no_upgrade_message_if_not_enrolled(self):
+        self.assertEqual(len(CourseEnrollment.enrollments_for_user(self.user)), 0)
+        self.assert_upgrade_message_not_displayed()
+
+    def test_no_upgrade_message_if_verified_track(self):
+        CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED)
+        self.assert_upgrade_message_not_displayed()
+
+    def test_no_upgrade_message_if_upgrade_deadline_passed(self):
+        self.verified_mode.expiration_datetime = datetime.now(UTC) - timedelta(days=20)
+        self.verified_mode.save()
+        self.assert_upgrade_message_not_displayed()
+
+    def test_no_upgrade_message_if_flag_disabled(self):
+        self.flag.everyone = False
+        self.flag.save()
+        CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT)
+        self.assert_upgrade_message_not_displayed()
+
+    def test_display_upgrade_message_if_audit_and_deadline_not_passed(self):
+        CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT)
+        self.assert_upgrade_message_displayed()
diff --git a/openedx/features/course_experience/views/course_home.py b/openedx/features/course_experience/views/course_home.py
index 2ca8316..026eeb9 100644
--- a/openedx/features/course_experience/views/course_home.py
+++ b/openedx/features/course_experience/views/course_home.py
@@ -9,6 +9,7 @@ from django.utils.decorators import method_decorator
 from django.views.decorators.cache import cache_control
 from django.views.decorators.csrf import ensure_csrf_cookie
 
+from commerce.utils import EcommerceService
 from courseware.access import has_access
 from courseware.courses import (
     can_self_enroll_in_course,
@@ -24,7 +25,7 @@ from student.models import CourseEnrollment
 from util.views import ensure_valid_course_key
 from web_fragments.fragment import Fragment
 
-from .. import LATEST_UPDATE_FLAG
+from .. import LATEST_UPDATE_FLAG, SHOW_UPGRADE_MSG_ON_COURSE_HOME
 from ..utils import get_course_outline_block_tree
 from .course_dates import CourseDatesFragmentView
 from .course_home_messages import CourseHomeMessageFragmentView
@@ -116,9 +117,10 @@ class CourseHomeFragmentView(EdxFragmentView):
 
         # Render the full content to enrolled users, as well as to course and global staff.
         # Unenrolled users who are not course or global staff are given only a subset.
+        enrollment = CourseEnrollment.get_enrollment(request.user, course_key)
         user_access = {
             'is_anonymous': request.user.is_anonymous(),
-            'is_enrolled': CourseEnrollment.is_enrolled(request.user, course_key),
+            'is_enrolled': enrollment is not None,
             'is_staff': has_access(request.user, 'staff', course_key),
         }
         if user_access['is_enrolled'] or user_access['is_staff']:
@@ -157,6 +159,22 @@ class CourseHomeFragmentView(EdxFragmentView):
             request, course_id=course_id, user_access=user_access, **kwargs
         )
 
+        # Get info for upgrade messaging
+        upgrade_price = None
+        upgrade_url = None
+
+        # TODO Add switch to control deployment
+        if SHOW_UPGRADE_MSG_ON_COURSE_HOME.is_enabled(course_key) and enrollment and enrollment.upgrade_deadline:
+            verified_mode = enrollment.verified_mode
+            if verified_mode:
+                upgrade_price = verified_mode.min_price
+
+                ecommerce_service = EcommerceService()
+                if ecommerce_service.is_enabled(request.user):
+                    upgrade_url = ecommerce_service.get_checkout_page_url(verified_mode.sku)
+                else:
+                    upgrade_url = reverse('verify_student_upgrade_and_verify', args=(course_key,))
+
         # Render the course home fragment
         context = {
             'request': request,
@@ -174,6 +192,8 @@ class CourseHomeFragmentView(EdxFragmentView):
             'course_sock_fragment': course_sock_fragment,
             'disable_courseware_js': True,
             'uses_pattern_library': True,
+            'upgrade_price': upgrade_price,
+            'upgrade_url': upgrade_url,
         }
         html = render_to_string('course_experience/course-home-fragment.html', context)
         return Fragment(html)
--
libgit2 0.26.0