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.
This commit is contained in:
@@ -133,7 +133,6 @@ class CourseEntitlement(TimeStampedModel):
|
||||
"""
|
||||
Represents a Student's Entitlement to a Course Run for a given Course.
|
||||
"""
|
||||
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL)
|
||||
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')
|
||||
|
||||
@@ -347,9 +347,10 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin):
|
||||
self.assertNotIn('<div class="prerequisites">', 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('<div class="course-entitlement-selection-container ">', 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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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('<div class="course-entitlement-selection-container"></div>');
|
||||
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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user