', response.content)
@patch('openedx.core.djangoapps.programs.utils.get_programs')
- @patch('student.views.get_visible_course_runs_for_entitlement')
+ @patch('student.views.get_visible_sessions_for_entitlement')
+ @patch('student.views.get_pseudo_session_for_entitlement')
@patch.object(CourseOverview, 'get_from_id')
- def test_unfulfilled_entitlement(self, mock_course_overview, mock_course_runs, mock_get_programs):
+ def test_unfulfilled_entitlement(self, mock_course_overview, mock_pseudo_session, mock_course_runs, mock_get_programs):
"""
When a learner has an unfulfilled entitlement, their course dashboard should have:
- a hidden 'View Course' button
@@ -369,13 +370,17 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin):
'type': 'verified'
}
]
+ mock_pseudo_session.return_value = {
+ 'key': 'course-v1:FAKE+FA1-MA1.X+3T2017',
+ 'type': 'verified'
+ }
response = self.client.get(self.path)
self.assertIn('class="enter-course hidden"', response.content)
self.assertIn('You must select a session to access the course.', response.content)
self.assertIn('
', response.content)
self.assertIn('Related Programs:', response.content)
- @patch('student.views.get_visible_course_runs_for_entitlement')
+ @patch('student.views.get_visible_sessions_for_entitlement')
@patch.object(CourseOverview, 'get_from_id')
def test_unfulfilled_expired_entitlement(self, mock_course_overview, mock_course_runs):
"""
@@ -468,7 +473,7 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin):
# self.assertNotIn(noAvailableSessions, response.content)
@patch('openedx.core.djangoapps.programs.utils.get_programs')
- @patch('student.views.get_visible_course_runs_for_entitlement')
+ @patch('student.views.get_visible_sessions_for_entitlement')
@patch.object(CourseOverview, 'get_from_id')
@patch('opaque_keys.edx.keys.CourseKey.from_string')
def test_fulfilled_entitlement(self, mock_course_key, mock_course_overview, mock_course_runs, mock_get_programs):
@@ -505,7 +510,7 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin):
self.assertIn('Related Programs:', response.content)
@patch('openedx.core.djangoapps.programs.utils.get_programs')
- @patch('student.views.get_visible_course_runs_for_entitlement')
+ @patch('student.views.get_visible_sessions_for_entitlement')
@patch.object(CourseOverview, 'get_from_id')
@patch('opaque_keys.edx.keys.CourseKey.from_string')
def test_fulfilled_expired_entitlement(self, mock_course_key, mock_course_overview, mock_course_runs, mock_get_programs):
diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index ba01a9c433..cfe10b9f8c 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -74,7 +74,9 @@ from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
# Note that this lives in LMS, so this dependency should be refactored.
from notification_prefs.views import enable_notifications
from openedx.core.djangoapps import monitoring_utils
-from openedx.core.djangoapps.catalog.utils import get_programs_with_type, get_visible_course_runs_for_entitlement
+from openedx.core.djangoapps.catalog.utils import (
+ get_programs_with_type, get_visible_sessions_for_entitlement, get_pseudo_session_for_entitlement
+)
from openedx.core.djangoapps.certificates.api import certificates_viewable_for_course
from openedx.core.djangoapps.credit.email_utils import get_credit_provider_display_names, make_providers_strings
from openedx.core.djangoapps.embargo import api as embargo_api
@@ -699,12 +701,18 @@ def dashboard(request):
course_enrollments = list(get_course_enrollments(user, site_org_whitelist, site_org_blacklist))
# Get the entitlements for the user and a mapping to all available sessions for that entitlement
+ # If an entitlement has no available sessions, pass through a mock course overview object
course_entitlements = list(CourseEntitlement.get_active_entitlements_for_user(user))
course_entitlement_available_sessions = {}
+ unfulfilled_entitlement_pseudo_sessions = {}
for course_entitlement in course_entitlements:
course_entitlement.update_expired_at()
- valid_course_runs = get_visible_course_runs_for_entitlement(course_entitlement)
- course_entitlement_available_sessions[str(course_entitlement.uuid)] = valid_course_runs
+ available_sessions = get_visible_sessions_for_entitlement(course_entitlement)
+ course_entitlement_available_sessions[str(course_entitlement.uuid)] = available_sessions
+ if not course_entitlement.enrollment_course_run:
+ # Unfulfilled entitlements need a mock session for metadata
+ pseudo_session = get_pseudo_session_for_entitlement(course_entitlement)
+ unfulfilled_entitlement_pseudo_sessions[str(course_entitlement.uuid)] = pseudo_session
# Record how many courses there are so that we can get a better
# understanding of usage patterns on prod.
@@ -915,6 +923,7 @@ def dashboard(request):
'course_enrollments': course_enrollments,
'course_entitlements': course_entitlements,
'course_entitlement_available_sessions': course_entitlement_available_sessions,
+ 'unfulfilled_entitlement_pseudo_sessions': unfulfilled_entitlement_pseudo_sessions,
'course_optouts': course_optouts,
'banner_account_activation_message': banner_account_activation_message,
'sidebar_account_activation_message': sidebar_account_activation_message,
diff --git a/lms/static/js/spec/learner_dashboard/course_entitlement_view_spec.js b/lms/static/js/spec/learner_dashboard/course_entitlement_view_spec.js
index caf546607d..5239ee6325 100644
--- a/lms/static/js/spec/learner_dashboard/course_entitlement_view_spec.js
+++ b/lms/static/js/spec/learner_dashboard/course_entitlement_view_spec.js
@@ -13,26 +13,33 @@ define([
selectOptions,
entitlementAvailableSessions,
initialSessionId,
+ alreadyEnrolled,
+ hasSessions,
entitlementUUID = 'a9aiuw76a4ijs43u18',
testSessionIds = ['test_session_id_1', 'test_session_id_2'];
- setupView = function(isAlreadyEnrolled) {
+ setupView = function(isAlreadyEnrolled, hasAvailableSessions) {
setFixtures('
');
+ alreadyEnrolled = (typeof isAlreadyEnrolled !== 'undefined') ? isAlreadyEnrolled : true;
+ hasSessions = (typeof hasAvailableSessions !== 'undefined') ? hasAvailableSessions : true;
- initialSessionId = isAlreadyEnrolled ? testSessionIds[0] : '';
- entitlementAvailableSessions = [{
- enrollment_end: null,
- start: '2019-02-05T05:00:00+00:00',
- pacing_type: 'instructor_paced',
- session_id: testSessionIds[0],
- end: null
- }, {
- enrollment_end: '2019-12-22T03:30:00Z',
- start: '2020-01-03T13:00:00+00:00',
- pacing_type: 'self_paced',
- session_id: testSessionIds[1],
- end: '2020-03-09T21:30:00+00:00'
- }];
+ initialSessionId = alreadyEnrolled ? testSessionIds[0] : '';
+ entitlementAvailableSessions = [];
+ if (hasSessions) {
+ entitlementAvailableSessions = [{
+ enrollment_end: null,
+ start: '2019-02-05T05:00:00+00:00',
+ pacing_type: 'instructor_paced',
+ session_id: testSessionIds[0],
+ end: null
+ }, {
+ enrollment_end: '2019-12-22T03:30:00Z',
+ start: '2020-01-03T13:00:00+00:00',
+ pacing_type: 'self_paced',
+ session_id: testSessionIds[1],
+ end: '2020-03-09T21:30:00+00:00'
+ }];
+ }
view = new CourseEntitlementView({
el: '.course-entitlement-selection-container',
@@ -95,6 +102,16 @@ define([
});
});
+ describe('Available Sessions Select - Unfulfilled Entitlement without available sessions', function() {
+ beforeEach(function() {
+ setupView(false, false);
+ });
+
+ it('Should notify user that more sessions are coming soon if none available.', function() {
+ expect(view.$('.action-header').text().includes('More sessions coming soon.')).toBe(true);
+ });
+ });
+
describe('Available Sessions Select - Fulfilled Entitlement', function() {
beforeEach(function() {
setupView(true);
diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html
index c0e36410eb..1961fbcff1 100644
--- a/lms/templates/dashboard.html
+++ b/lms/templates/dashboard.html
@@ -161,17 +161,16 @@ from student.models import CourseEnrollment
# If the user has a fulfilled entitlement, pass through the entitlements CourseEnrollment object
enrollment = entitlement_session
else:
- # If the user has an unfulfilled entitlement, pass through a bare CourseEnrollment object built off of the next available session
- upcoming_sessions = course_entitlement_available_sessions[str(entitlement.uuid)]
- next_session = upcoming_sessions[0] if upcoming_sessions else None
- if not next_session:
+ # If the user has an unfulfilled entitlement, pass through a bare CourseEnrollment object to populate card with metadata
+ pseudo_session = unfulfilled_entitlement_pseudo_sessions[str(entitlement.uuid)]
+ if not pseudo_session:
continue
- enrollment = CourseEnrollment(user=user, course_id=next_session['key'], mode=next_session['type'])
+ enrollment = CourseEnrollment(user=user, course_id=pseudo_session['key'], mode=pseudo_session['type'])
# We only show email settings for entitlement cards if the entitlement has an associated enrollment
show_email_settings = is_fulfilled_entitlement and (entitlement_session.course_id in show_email_settings_for)
else:
show_email_settings = (enrollment.course_id in show_email_settings_for)
-
+
session_id = enrollment.course_id
show_courseware_link = (session_id in show_courseware_links_for)
cert_status = cert_statuses.get(session_id)
diff --git a/openedx/core/djangoapps/catalog/utils.py b/openedx/core/djangoapps/catalog/utils.py
index 4575b931f0..a407e5baf1 100644
--- a/openedx/core/djangoapps/catalog/utils.py
+++ b/openedx/core/djangoapps/catalog/utils.py
@@ -243,9 +243,26 @@ def get_course_runs_for_course(course_uuid):
return []
-def get_visible_course_runs_for_entitlement(entitlement):
+def get_pseudo_session_for_entitlement(entitlement):
"""
- Returns only the course runs that the user can currently enroll in.
+ This function is used to pass pseudo-data to the front end, returning a single session, regardless of whether that
+ session is currently available.
+
+ First tries to return the first available session, followed by the first session regardless of availability.
+ Returns None if there are no sessions for that course.
+ """
+ sessions_for_course = get_course_runs_for_course(entitlement.course_uuid)
+ available_sessions = get_fulfillable_course_runs_for_entitlement(entitlement, sessions_for_course)
+ if available_sessions:
+ return available_sessions[0]
+ if sessions_for_course:
+ return sessions_for_course[0]
+ return None
+
+
+def get_visible_sessions_for_entitlement(entitlement):
+ """
+ Takes an entitlement object and returns the course runs that a user can currently enroll in.
"""
sessions_for_course = get_course_runs_for_course(entitlement.course_uuid)
return get_fulfillable_course_runs_for_entitlement(entitlement, sessions_for_course)
diff --git a/themes/edx.org/lms/templates/dashboard.html b/themes/edx.org/lms/templates/dashboard.html
index 1da55c5130..7b25e71cd6 100644
--- a/themes/edx.org/lms/templates/dashboard.html
+++ b/themes/edx.org/lms/templates/dashboard.html
@@ -157,12 +157,11 @@ from student.models import CourseEnrollment
# If the user has a fulfilled entitlement, pass through the entitlements CourseEnrollment object
enrollment = entitlement_session
else:
- # If the user has an unfulfilled entitlement, pass through a bare CourseEnrollment object built off of the next available session
- upcoming_sessions = course_entitlement_available_sessions[str(entitlement.uuid)]
- next_session = upcoming_sessions[0] if upcoming_sessions else None
- if not next_session:
+ # If the user has an unfulfilled entitlement, pass through a bare CourseEnrollment object to populate card with metadata
+ pseudo_session = unfulfilled_entitlement_pseudo_sessions[str(entitlement.uuid)]
+ if not pseudo_session:
continue
- enrollment = CourseEnrollment(user=user, course_id=next_session['key'], mode=next_session['type'])
+ enrollment = CourseEnrollment(user=user, course_id=pseudo_session['key'], mode=pseudo_session['type'])
# We only show email settings for entitlement cards if the entitlement has an associated enrollment
show_email_settings = is_fulfilled_entitlement and (entitlement_session.course_id in show_email_settings_for)
else: