ECOM-4219 - Add the course states to course cards and make sure the display follows course states (#12844)
Please enter the commit message for your changes. Lines starting
This commit is contained in:
@@ -15,19 +15,46 @@
|
||||
}
|
||||
},
|
||||
|
||||
getUnselectedRunMode: function(runModes) {
|
||||
if(runModes && runModes.length > 0){
|
||||
return {
|
||||
course_image_url: runModes[0].course_image_url,
|
||||
marketing_url: runModes[0].marketing_url,
|
||||
is_enrollment_open: runModes[0].is_enrollment_open,
|
||||
enrollment_open_date: runModes[0].enrollment_open_date
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
||||
getRunMode: function(runModes){
|
||||
//we should populate our model by looking at the run_modes
|
||||
if (runModes.length > 0){
|
||||
if(runModes.length === 1){
|
||||
return runModes[0];
|
||||
var enrolled_mode = _.findWhere(runModes, {is_enrolled: true}),
|
||||
openEnrollmentRunModes = this.getEnrollableRunModes(),
|
||||
desiredRunMode;
|
||||
//we populate our model by looking at the run_modes
|
||||
if (enrolled_mode){
|
||||
// If we have a run_mode we are already enrolled in,
|
||||
// return that one always
|
||||
desiredRunMode = enrolled_mode;
|
||||
} else if (openEnrollmentRunModes.length > 0){
|
||||
if(openEnrollmentRunModes.length === 1){
|
||||
desiredRunMode = openEnrollmentRunModes[0];
|
||||
}else{
|
||||
//We need to implement logic here to select the
|
||||
//most relevant run mode for the student to enroll
|
||||
return runModes[0];
|
||||
desiredRunMode = this.getUnselectedRunMode(openEnrollmentRunModes);
|
||||
}
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
desiredRunMode = this.getUnselectedRunMode(runModes);
|
||||
}
|
||||
return desiredRunMode;
|
||||
},
|
||||
|
||||
getEnrollableRunModes: function(){
|
||||
return _.where(this.context.run_modes,
|
||||
{
|
||||
is_enrollment_open: true,
|
||||
is_enrolled: false,
|
||||
is_course_ended: false
|
||||
});
|
||||
},
|
||||
|
||||
setActiveRunMode: function(runMode){
|
||||
@@ -38,18 +65,29 @@
|
||||
course_key: runMode.course_key,
|
||||
course_url: runMode.course_url || '',
|
||||
display_name: this.context.display_name,
|
||||
start_date: runMode.start_date,
|
||||
end_date: runMode.end_date,
|
||||
is_enrolled: runMode.is_enrolled,
|
||||
is_enrollment_open: runMode.is_enrollment_open,
|
||||
key: this.context.key,
|
||||
marketing_url: runMode.marketing_url || '',
|
||||
is_course_ended: runMode.is_course_ended,
|
||||
mode_slug: runMode.mode_slug,
|
||||
run_key: runMode.run_key,
|
||||
start_date: runMode.start_date
|
||||
enrollment_open_date: runMode.enrollment_open_date || '',
|
||||
enrollable_run_modes: this.getEnrollableRunModes()
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
setUnselected: function(){
|
||||
//This should be called to reset the model
|
||||
//back to the unselected state
|
||||
var unselectedMode = this.getUnselectedRunMode(
|
||||
this.get('enrollable_run_modes'));
|
||||
this.setActiveRunMode(unselectedMode);
|
||||
},
|
||||
|
||||
updateRun: function(runKey){
|
||||
var selectedRun = _.findWhere(this.get('run_modes'), {run_key: runKey});
|
||||
if (selectedRun){
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
return Backbone.Model.extend({
|
||||
defaults: {
|
||||
course_id: '',
|
||||
optIn: false,
|
||||
optIn: false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,17 +29,14 @@
|
||||
this.enrollModel = options.enrollModel;
|
||||
this.urlModel = options.urlModel;
|
||||
this.render();
|
||||
if(this.urlModel){
|
||||
if (this.urlModel){
|
||||
this.trackSelectionUrl = this.urlModel.get('track_selection_url');
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var filledTemplate;
|
||||
if (this.$parentEl &&
|
||||
this.enrollModel &&
|
||||
this.model.get('course_key')){
|
||||
|
||||
if (this.$parentEl && this.enrollModel){
|
||||
filledTemplate = this.tpl(this.model.toJSON());
|
||||
HtmlUtils.setHtml(this.$el, filledTemplate);
|
||||
HtmlUtils.setHtml(this.$parentEl, HtmlUtils.HTML(this.$el));
|
||||
@@ -48,7 +45,9 @@
|
||||
|
||||
handleEnroll: function(){
|
||||
//Enrollment click event handled here
|
||||
if (!this.model.get('is_enrolled')){
|
||||
if (!this.model.get('course_key')){
|
||||
this.$('.select-error').css('visibility','visible');
|
||||
} else if (!this.model.get('is_enrolled')){
|
||||
// actually enroll
|
||||
this.enrollModel.save({
|
||||
course_id: this.model.get('course_key')
|
||||
@@ -65,6 +64,9 @@
|
||||
runKey = $(event.target).val();
|
||||
if (runKey){
|
||||
this.model.updateRun(runKey);
|
||||
} else {
|
||||
//Set back the unselected states
|
||||
this.model.setUnselected();
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -74,7 +76,7 @@
|
||||
if (this.trackSelectionUrl) {
|
||||
// Go to track selection page
|
||||
this.redirect( this.trackSelectionUrl + courseKey );
|
||||
}else{
|
||||
} else {
|
||||
this.model.set({
|
||||
is_enrolled: true
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ define([
|
||||
},
|
||||
run_modes: [{
|
||||
start_date: 'Apr 25, 2016',
|
||||
end_date: 'Jun 13, 2016',
|
||||
end_date: 'Jun 13, 2019',
|
||||
course_key: 'course-v1:ANUx+ANU-ASTRO1x+3T2015',
|
||||
course_url: 'http://localhost:8000/courses/course-v1:edX+DemoX+Demo_Course/info',
|
||||
marketing_url: 'https://www.edx.org/course/astrophysics-exploring',
|
||||
@@ -29,12 +29,15 @@ define([
|
||||
run_key: '2T2016',
|
||||
course_started: true,
|
||||
is_enrolled: true,
|
||||
certificate_url: ''
|
||||
is_course_ended: false,
|
||||
is_enrollment_open: true,
|
||||
certificate_url: '',
|
||||
enrollment_open_date: 'Mar 03, 2016'
|
||||
}]
|
||||
},
|
||||
|
||||
setupView = function(data, isEnrolled){
|
||||
context.run_modes[0].is_enrolled = isEnrolled;
|
||||
data.run_modes[0].is_enrolled = isEnrolled;
|
||||
setFixtures('<div class="course-card card"></div>');
|
||||
courseCardModel = new CourseCardModel(data);
|
||||
view = new CourseCardView({
|
||||
@@ -94,6 +97,28 @@ define([
|
||||
expect(view.$('.certificate-status').length).toEqual(1);
|
||||
expect(view.$('.certificate-status .cta-secondary').attr('href')).toEqual(certUrl);
|
||||
});
|
||||
|
||||
it('should render the course card with coming soon', function(){
|
||||
view.remove();
|
||||
context.run_modes[0].is_enrollment_open = false;
|
||||
setupView(context, false);
|
||||
expect(view.$('.header-img').attr('src')).toEqual(context.run_modes[0].course_image_url);
|
||||
expect(view.$('.course-details .course-title').text().trim()).toEqual(context.display_name);
|
||||
expect(view.$('.course-details .course-title-link').length).toBe(0);
|
||||
expect(view.$('.course-details .course-text .course-key').html()).toEqual(context.key);
|
||||
expect(view.$('.course-details .course-text .run-period').length).toBe(0);
|
||||
expect(view.$('.no-action-message').text().trim()).toBe('Coming Soon');
|
||||
expect(view.$('.enroll-open-date').text().trim())
|
||||
.toBe(context.run_modes[0].enrollment_open_date);
|
||||
});
|
||||
|
||||
it('should render if enrollment_open_date is not provided', function(){
|
||||
view.remove();
|
||||
context.run_modes[0].is_enrollment_open = true;
|
||||
delete context.run_modes[0].enrollment_open_date;
|
||||
setupView(context, false);
|
||||
validateCourseInfoDisplay();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -21,9 +21,11 @@ define([
|
||||
course_url: 'http://localhost:8000/courses/course-v1:edX+DemoX+Demo_Course/info',
|
||||
course_image_url: 'http://test.com/image1',
|
||||
marketing_url: 'http://test.com/image2',
|
||||
is_course_ended: false,
|
||||
mode_slug: 'audit',
|
||||
run_key: '2T2016',
|
||||
is_enrolled: false
|
||||
is_enrolled: false,
|
||||
is_enrollment_open: true
|
||||
}],
|
||||
multiRunModeList = [{
|
||||
start_date: 'May 21, 2015',
|
||||
@@ -33,8 +35,10 @@ define([
|
||||
course_image_url: 'http://test.com/run_2_image_1',
|
||||
marketing_url: 'http://test.com/run_2_image_2',
|
||||
mode_slug: 'verified',
|
||||
is_course_ended: false,
|
||||
run_key: '1T2015',
|
||||
is_enrolled: false
|
||||
is_enrolled: false,
|
||||
is_enrollment_open: true,
|
||||
},{
|
||||
start_date: 'Sep 22, 2015',
|
||||
end_date: 'Dec 28, 2015',
|
||||
@@ -42,9 +46,11 @@ define([
|
||||
course_url: 'http://localhost:8000/courses/course-v1:edX+DemoX+Demo_Course/info',
|
||||
course_image_url: 'http://test.com/run_3_image_1',
|
||||
marketing_url: 'http://test.com/run_3_image_2',
|
||||
is_course_ended: false,
|
||||
mode_slug: 'verified',
|
||||
run_key: '2T2015',
|
||||
is_enrolled: false
|
||||
is_enrolled: false,
|
||||
is_enrollment_open: true
|
||||
}],
|
||||
context = {
|
||||
display_name: 'Edx Demo course',
|
||||
@@ -117,13 +123,14 @@ define([
|
||||
it('should render run selection drop down if mulitple run available', function(){
|
||||
setupView(multiRunModeList);
|
||||
expect(view.$('.run-select').length).toBe(1);
|
||||
expect(view.$('.run-select').val()).toEqual(multiRunModeList[0].run_key);
|
||||
expect(view.$('.run-select').val()).toEqual('');
|
||||
expect(view.$('.run-select option').length).toBe(3);
|
||||
});
|
||||
|
||||
it('should switch run context if dropdown selection changed', function(){
|
||||
setupView(multiRunModeList);
|
||||
spyOn(courseCardModel, 'updateRun').and.callThrough();
|
||||
expect(view.$('.run-select').val()).toEqual(multiRunModeList[0].run_key);
|
||||
expect(view.$('.run-select').val()).toEqual('');
|
||||
view.$('.run-select').val(multiRunModeList[1].run_key);
|
||||
view.$('.run-select').trigger("change");
|
||||
expect(view.$('.run-select').val()).toEqual(multiRunModeList[1].run_key);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
padding: $baseline/2 $baseline;
|
||||
}
|
||||
|
||||
.course-image-link {
|
||||
.course-image-container{
|
||||
@include float(left);
|
||||
|
||||
.header-img {
|
||||
@@ -47,8 +47,26 @@
|
||||
margin-bottom: $baseline/2;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.run-select-container {
|
||||
.select-error{
|
||||
color: palette(error, base);
|
||||
margin-bottom: $baseline/4;
|
||||
font-size: font-size(small);
|
||||
visibility: hidden;
|
||||
}
|
||||
.no-action-message{
|
||||
margin-bottom: $baseline/2;
|
||||
color: palette(grayscale, black);
|
||||
font-size: font-size(large);
|
||||
text-align: center;
|
||||
}
|
||||
.enrollment-opens{
|
||||
text-align: center;
|
||||
margin-bottom: $baseline/2;
|
||||
}
|
||||
.enroll-open-date{
|
||||
text-align: center;
|
||||
}
|
||||
.run-select-container{
|
||||
margin-bottom: $baseline;
|
||||
|
||||
.run-select {
|
||||
@@ -61,8 +79,8 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.view-course-link {
|
||||
width: $baseline*10;
|
||||
.view-course-link{
|
||||
min-width: $baseline*10;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,36 @@
|
||||
<div class="section">
|
||||
<div class="course-meta-container col-12 md-col-8 sm-col-12">
|
||||
<a href="<%- course_url %>" class="course-image-link">
|
||||
<img
|
||||
class="header-img"
|
||||
src="<%- course_image_url %>"
|
||||
alt="<%= interpolate(gettext('%(courseName)s Home Page.'), {courseName: display_name}, true) %>"/>
|
||||
</a>
|
||||
<div class="course-image-container">
|
||||
<% if (course_url){ %>
|
||||
<a href="<%- course_url %>" class="course-image-link">
|
||||
<img
|
||||
class="header-img"
|
||||
src="<%- course_image_url %>"
|
||||
alt="<%= interpolate(gettext('%(courseName)s Home Page.'), {courseName: display_name}, true) %>"/>
|
||||
</a>
|
||||
<% } else { %>
|
||||
<img
|
||||
class="header-img"
|
||||
src="<%- course_image_url %>"
|
||||
alt="" />
|
||||
<% } %>
|
||||
|
||||
</div>
|
||||
<div class="course-details">
|
||||
<h3 class="course-title">
|
||||
<a href="<%- course_url %>" class="course-title-link">
|
||||
<% if (course_url){ %>
|
||||
<a href="<%- course_url %>" class="course-title-link">
|
||||
<%- display_name %>
|
||||
</a>
|
||||
<% }else{ %>
|
||||
<%- display_name %>
|
||||
</a>
|
||||
<% } %>
|
||||
</h3>
|
||||
<div class="course-text">
|
||||
<span class="run-period"><%- start_date %> - <%- end_date %></span>
|
||||
-
|
||||
<% if (start_date && end_date){ %>
|
||||
<span class="run-period"><%- start_date %> - <%- end_date %></span>
|
||||
-
|
||||
<% } %>
|
||||
<span class="course-key"><%- key %></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,32 +1,58 @@
|
||||
<% if (is_enrolled){ %>
|
||||
<div class="enrollment-info"><%- gettext('enrolled') %></div>
|
||||
<a href="<%- course_url %>" class="btn-neutral btn view-course-link">
|
||||
<%- gettext('View Course') %>
|
||||
</a>
|
||||
<% if (is_enrollment_open || is_course_ended){ %>
|
||||
<a href="<%- course_url %>" class="btn-neutral btn view-course-link">
|
||||
<% if (is_enrollment_open){ %>
|
||||
<%- gettext('View Course') %>
|
||||
<% } else if (course_ended){ %>
|
||||
<%- gettext('View Archived Course') %>
|
||||
<% } %>
|
||||
</a>
|
||||
<% } %>
|
||||
<% }else{ %>
|
||||
<div class="enrollment-info"><%- gettext('not enrolled') %></div>
|
||||
<% if (run_modes.length > 1){ %>
|
||||
<div class="run-select-container">
|
||||
<label class="sr-only" for="select-<%- course_key %>-run">Select Course Run</label>
|
||||
<select id="select-<%- course_key %>-run" class="run-select" autocomplete="off">
|
||||
<% _.each (run_modes, function(runMode){ %>
|
||||
<option
|
||||
value="<%- runMode.run_key %>"
|
||||
<% if(run_key === runMode.run_key){ %>
|
||||
selected="selected"
|
||||
<% }%>
|
||||
>
|
||||
<%= interpolate(
|
||||
gettext('Starts %(start)s'),
|
||||
{ start: runMode.start_date },
|
||||
true)
|
||||
%>
|
||||
<% if (enrollable_run_modes.length > 0){ %>
|
||||
<div class="enrollment-info"><%- gettext('not enrolled') %></div>
|
||||
<% if (enrollable_run_modes.length > 1){ %>
|
||||
<div class="run-select-container">
|
||||
<div class="select-error">
|
||||
<%- gettext('Please select a course date') %>
|
||||
</div>
|
||||
<label class="sr-only" for="select-<%- course_key %>-run">
|
||||
<%- gettext('Select Course Run') %>
|
||||
</label>
|
||||
<select id="select-<%- course_key %>-run" class="run-select" autocomplete="off">
|
||||
<option value="" selected="selected">
|
||||
<%- gettext('Choose Course Date') %>
|
||||
</option>
|
||||
<% }); %>
|
||||
</select>
|
||||
<% _.each (enrollable_run_modes, function(runMode){ %>
|
||||
<option
|
||||
value="<%- runMode.run_key %>"
|
||||
<% if (run_key === runMode.run_key){ %>
|
||||
selected="selected"
|
||||
<% }%>
|
||||
>
|
||||
<%= interpolate(
|
||||
gettext('Starts %(start)s'),
|
||||
{ start: runMode.start_date },
|
||||
true)
|
||||
%>
|
||||
</option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
<% } %>
|
||||
<button type="button" class="btn-brand btn cta-primary enroll-button">
|
||||
<%- gettext('Enroll Now') %>
|
||||
</button>
|
||||
<% } else {%>
|
||||
<div class="no-action-message">
|
||||
<%- gettext('Coming Soon') %>
|
||||
</div>
|
||||
<div class="enrollment-opens">
|
||||
<%- gettext('Enrollment Opens') %>
|
||||
</div>
|
||||
<div class="enroll-open-date">
|
||||
<%- enrollment_open_date %>
|
||||
</div>
|
||||
<% } %>
|
||||
<button type="button" class="btn-brand btn cta-primary enroll-button">
|
||||
<%- gettext('Enroll Now') %>
|
||||
</button>
|
||||
<% } %>
|
||||
|
||||
@@ -702,7 +702,6 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
def _assert_supplemented(self, actual, **kwargs):
|
||||
"""DRY helper used to verify that program data is extended correctly."""
|
||||
course_overview = CourseOverview.get_from_id(self.course.id) # pylint: disable=no-member
|
||||
|
||||
run_mode = dict(
|
||||
factories.RunMode(
|
||||
course_key=unicode(self.course.id), # pylint: disable=no-member
|
||||
@@ -710,6 +709,7 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
course_image_url=course_overview.course_image_url,
|
||||
start_date=self.course.start.strftime(self.human_friendly_format),
|
||||
end_date=self.course.end.strftime(self.human_friendly_format),
|
||||
is_course_ended=self.course.end < timezone.now(),
|
||||
is_enrolled=False,
|
||||
is_enrollment_open=True,
|
||||
marketing_url='',
|
||||
@@ -745,7 +745,15 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
self._assert_supplemented(data, is_enrollment_open=is_enrollment_open)
|
||||
if is_enrollment_open:
|
||||
self._assert_supplemented(
|
||||
data,
|
||||
is_enrollment_open=is_enrollment_open)
|
||||
else:
|
||||
self._assert_supplemented(
|
||||
data,
|
||||
is_enrollment_open=is_enrollment_open,
|
||||
enrollment_open_date=self.course.enrollment_start.strftime(self.human_friendly_format))
|
||||
|
||||
@ddt.data(True, False)
|
||||
@mock.patch(UTILS_MODULE + '.certificate_api.certificate_downloadable_status')
|
||||
@@ -792,3 +800,11 @@ class TestSupplementProgramData(ProgramsApiConfigMixin, ModuleStoreTestCase):
|
||||
mock_get_organization_by_short_name.return_value = {'logo': None}
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
self.assertEqual(data['organizations'][0].get('img'), None)
|
||||
|
||||
@ddt.data(-1, 0, 1)
|
||||
def test_course_course_ended(self, days_offset):
|
||||
self.course.end = timezone.now() + datetime.timedelta(days=days_offset)
|
||||
self.course = self.update_course(self.course, self.user.id) # pylint: disable=no-member
|
||||
data = utils.supplement_program_data(self.program, self.user)
|
||||
|
||||
self._assert_supplemented(data)
|
||||
|
||||
@@ -348,6 +348,7 @@ def supplement_program_data(program_data, user):
|
||||
end_date = course_overview.end or datetime.datetime.max.replace(tzinfo=pytz.UTC)
|
||||
run_mode['start_date'] = start_date.strftime(human_friendly_format)
|
||||
run_mode['end_date'] = end_date.strftime(human_friendly_format)
|
||||
run_mode['is_course_ended'] = end_date < timezone.now()
|
||||
|
||||
run_mode['is_enrolled'] = CourseEnrollment.is_enrolled(user, course_key)
|
||||
|
||||
@@ -355,6 +356,9 @@ def supplement_program_data(program_data, user):
|
||||
enrollment_end = course_overview.enrollment_end or datetime.datetime.max.replace(tzinfo=pytz.UTC)
|
||||
is_enrollment_open = enrollment_start <= timezone.now() < enrollment_end
|
||||
run_mode['is_enrollment_open'] = is_enrollment_open
|
||||
if not is_enrollment_open:
|
||||
# Only render this enrollment open date if the enrollment open is in the future
|
||||
run_mode['enrollment_open_date'] = enrollment_start.strftime(human_friendly_format)
|
||||
|
||||
# TODO: Currently unavailable on LMS.
|
||||
run_mode['marketing_url'] = ''
|
||||
|
||||
Reference in New Issue
Block a user