Commit 4911bf80 by Simon Chen Committed by GitHub

ECOM-4219 - Add the course states to course cards and make sure the display…

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