Skip to content
Snippets Groups Projects
Commit b4041730 authored by Harry Rein's avatar Harry Rein
Browse files

Show more sessions coming soon on course dashboard.

LEARNER-3808

Ensures that users can see their entitlement purchase whether there
are available sessions or not. Notifies them with a message stating that
more sessions are coming soon, as is currently implemented on the programs
dashboard.
parent e7c869f8
No related merge requests found
...@@ -133,7 +133,6 @@ class CourseEntitlement(TimeStampedModel): ...@@ -133,7 +133,6 @@ class CourseEntitlement(TimeStampedModel):
""" """
Represents a Student's Entitlement to a Course Run for a given Course. Represents a Student's Entitlement to a Course Run for a given Course.
""" """
user = models.ForeignKey(settings.AUTH_USER_MODEL) user = models.ForeignKey(settings.AUTH_USER_MODEL)
uuid = models.UUIDField(default=uuid_tools.uuid4, editable=False, unique=True) uuid = models.UUIDField(default=uuid_tools.uuid4, editable=False, unique=True)
course_uuid = models.UUIDField(help_text='UUID for the Course, not the Course Run') course_uuid = models.UUIDField(help_text='UUID for the Course, not the Course Run')
......
...@@ -347,9 +347,10 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin): ...@@ -347,9 +347,10 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin):
self.assertNotIn('<div class="prerequisites">', response.content) self.assertNotIn('<div class="prerequisites">', response.content)
@patch('openedx.core.djangoapps.programs.utils.get_programs') @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') @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: When a learner has an unfulfilled entitlement, their course dashboard should have:
- a hidden 'View Course' button - a hidden 'View Course' button
...@@ -369,13 +370,17 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin): ...@@ -369,13 +370,17 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin):
'type': 'verified' 'type': 'verified'
} }
] ]
mock_pseudo_session.return_value = {
'key': 'course-v1:FAKE+FA1-MA1.X+3T2017',
'type': 'verified'
}
response = self.client.get(self.path) response = self.client.get(self.path)
self.assertIn('class="enter-course hidden"', response.content) self.assertIn('class="enter-course hidden"', response.content)
self.assertIn('You must select a session to access the course.', response.content) self.assertIn('You must select a session to access the course.', response.content)
self.assertIn('<div class="course-entitlement-selection-container ">', response.content) self.assertIn('<div class="course-entitlement-selection-container ">', response.content)
self.assertIn('Related Programs:', 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') @patch.object(CourseOverview, 'get_from_id')
def test_unfulfilled_expired_entitlement(self, mock_course_overview, mock_course_runs): def test_unfulfilled_expired_entitlement(self, mock_course_overview, mock_course_runs):
""" """
...@@ -468,7 +473,7 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin): ...@@ -468,7 +473,7 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin):
# self.assertNotIn(noAvailableSessions, response.content) # self.assertNotIn(noAvailableSessions, response.content)
@patch('openedx.core.djangoapps.programs.utils.get_programs') @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.object(CourseOverview, 'get_from_id')
@patch('opaque_keys.edx.keys.CourseKey.from_string') @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): 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): ...@@ -505,7 +510,7 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin):
self.assertIn('Related Programs:', response.content) self.assertIn('Related Programs:', response.content)
@patch('openedx.core.djangoapps.programs.utils.get_programs') @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.object(CourseOverview, 'get_from_id')
@patch('opaque_keys.edx.keys.CourseKey.from_string') @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): def test_fulfilled_expired_entitlement(self, mock_course_key, mock_course_overview, mock_course_runs, mock_get_programs):
......
...@@ -74,7 +74,9 @@ from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification ...@@ -74,7 +74,9 @@ from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
# Note that this lives in LMS, so this dependency should be refactored. # Note that this lives in LMS, so this dependency should be refactored.
from notification_prefs.views import enable_notifications from notification_prefs.views import enable_notifications
from openedx.core.djangoapps import monitoring_utils 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.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.credit.email_utils import get_credit_provider_display_names, make_providers_strings
from openedx.core.djangoapps.embargo import api as embargo_api from openedx.core.djangoapps.embargo import api as embargo_api
...@@ -699,12 +701,18 @@ def dashboard(request): ...@@ -699,12 +701,18 @@ def dashboard(request):
course_enrollments = list(get_course_enrollments(user, site_org_whitelist, site_org_blacklist)) 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 # 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_entitlements = list(CourseEntitlement.get_active_entitlements_for_user(user))
course_entitlement_available_sessions = {} course_entitlement_available_sessions = {}
unfulfilled_entitlement_pseudo_sessions = {}
for course_entitlement in course_entitlements: for course_entitlement in course_entitlements:
course_entitlement.update_expired_at() course_entitlement.update_expired_at()
valid_course_runs = get_visible_course_runs_for_entitlement(course_entitlement) available_sessions = get_visible_sessions_for_entitlement(course_entitlement)
course_entitlement_available_sessions[str(course_entitlement.uuid)] = valid_course_runs 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 # Record how many courses there are so that we can get a better
# understanding of usage patterns on prod. # understanding of usage patterns on prod.
...@@ -915,6 +923,7 @@ def dashboard(request): ...@@ -915,6 +923,7 @@ def dashboard(request):
'course_enrollments': course_enrollments, 'course_enrollments': course_enrollments,
'course_entitlements': course_entitlements, 'course_entitlements': course_entitlements,
'course_entitlement_available_sessions': course_entitlement_available_sessions, 'course_entitlement_available_sessions': course_entitlement_available_sessions,
'unfulfilled_entitlement_pseudo_sessions': unfulfilled_entitlement_pseudo_sessions,
'course_optouts': course_optouts, 'course_optouts': course_optouts,
'banner_account_activation_message': banner_account_activation_message, 'banner_account_activation_message': banner_account_activation_message,
'sidebar_account_activation_message': sidebar_account_activation_message, 'sidebar_account_activation_message': sidebar_account_activation_message,
......
...@@ -13,26 +13,33 @@ define([ ...@@ -13,26 +13,33 @@ define([
selectOptions, selectOptions,
entitlementAvailableSessions, entitlementAvailableSessions,
initialSessionId, initialSessionId,
alreadyEnrolled,
hasSessions,
entitlementUUID = 'a9aiuw76a4ijs43u18', entitlementUUID = 'a9aiuw76a4ijs43u18',
testSessionIds = ['test_session_id_1', 'test_session_id_2']; testSessionIds = ['test_session_id_1', 'test_session_id_2'];
setupView = function(isAlreadyEnrolled) { setupView = function(isAlreadyEnrolled, hasAvailableSessions) {
setFixtures('<div class="course-entitlement-selection-container"></div>'); setFixtures('<div class="course-entitlement-selection-container"></div>');
alreadyEnrolled = (typeof isAlreadyEnrolled !== 'undefined') ? isAlreadyEnrolled : true;
initialSessionId = isAlreadyEnrolled ? testSessionIds[0] : ''; hasSessions = (typeof hasAvailableSessions !== 'undefined') ? hasAvailableSessions : true;
entitlementAvailableSessions = [{
enrollment_end: null, initialSessionId = alreadyEnrolled ? testSessionIds[0] : '';
start: '2019-02-05T05:00:00+00:00', entitlementAvailableSessions = [];
pacing_type: 'instructor_paced', if (hasSessions) {
session_id: testSessionIds[0], entitlementAvailableSessions = [{
end: null enrollment_end: null,
}, { start: '2019-02-05T05:00:00+00:00',
enrollment_end: '2019-12-22T03:30:00Z', pacing_type: 'instructor_paced',
start: '2020-01-03T13:00:00+00:00', session_id: testSessionIds[0],
pacing_type: 'self_paced', end: null
session_id: testSessionIds[1], }, {
end: '2020-03-09T21:30:00+00:00' 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({ view = new CourseEntitlementView({
el: '.course-entitlement-selection-container', el: '.course-entitlement-selection-container',
...@@ -95,6 +102,16 @@ define([ ...@@ -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() { describe('Available Sessions Select - Fulfilled Entitlement', function() {
beforeEach(function() { beforeEach(function() {
setupView(true); setupView(true);
......
...@@ -161,17 +161,16 @@ from student.models import CourseEnrollment ...@@ -161,17 +161,16 @@ from student.models import CourseEnrollment
# If the user has a fulfilled entitlement, pass through the entitlements CourseEnrollment object # If the user has a fulfilled entitlement, pass through the entitlements CourseEnrollment object
enrollment = entitlement_session enrollment = entitlement_session
else: else:
# If the user has an unfulfilled entitlement, pass through a bare CourseEnrollment object built off of the next available session # If the user has an unfulfilled entitlement, pass through a bare CourseEnrollment object to populate card with metadata
upcoming_sessions = course_entitlement_available_sessions[str(entitlement.uuid)] pseudo_session = unfulfilled_entitlement_pseudo_sessions[str(entitlement.uuid)]
next_session = upcoming_sessions[0] if upcoming_sessions else None if not pseudo_session:
if not next_session:
continue 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 # 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) show_email_settings = is_fulfilled_entitlement and (entitlement_session.course_id in show_email_settings_for)
else: else:
show_email_settings = (enrollment.course_id in show_email_settings_for) show_email_settings = (enrollment.course_id in show_email_settings_for)
session_id = enrollment.course_id session_id = enrollment.course_id
show_courseware_link = (session_id in show_courseware_links_for) show_courseware_link = (session_id in show_courseware_links_for)
cert_status = cert_statuses.get(session_id) cert_status = cert_statuses.get(session_id)
......
...@@ -243,9 +243,26 @@ def get_course_runs_for_course(course_uuid): ...@@ -243,9 +243,26 @@ def get_course_runs_for_course(course_uuid):
return [] 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) sessions_for_course = get_course_runs_for_course(entitlement.course_uuid)
return get_fulfillable_course_runs_for_entitlement(entitlement, sessions_for_course) return get_fulfillable_course_runs_for_entitlement(entitlement, sessions_for_course)
......
...@@ -157,12 +157,11 @@ from student.models import CourseEnrollment ...@@ -157,12 +157,11 @@ from student.models import CourseEnrollment
# If the user has a fulfilled entitlement, pass through the entitlements CourseEnrollment object # If the user has a fulfilled entitlement, pass through the entitlements CourseEnrollment object
enrollment = entitlement_session enrollment = entitlement_session
else: else:
# If the user has an unfulfilled entitlement, pass through a bare CourseEnrollment object built off of the next available session # If the user has an unfulfilled entitlement, pass through a bare CourseEnrollment object to populate card with metadata
upcoming_sessions = course_entitlement_available_sessions[str(entitlement.uuid)] pseudo_session = unfulfilled_entitlement_pseudo_sessions[str(entitlement.uuid)]
next_session = upcoming_sessions[0] if upcoming_sessions else None if not pseudo_session:
if not next_session:
continue 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 # 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) show_email_settings = is_fulfilled_entitlement and (entitlement_session.course_id in show_email_settings_for)
else: else:
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment