diff --git a/lms/static/js/learner_dashboard/views/course_card_view.js b/lms/static/js/learner_dashboard/views/course_card_view.js
index db7cf04bc2..399b829933 100644
--- a/lms/static/js/learner_dashboard/views/course_card_view.js
+++ b/lms/static/js/learner_dashboard/views/course_card_view.js
@@ -38,6 +38,9 @@
this.enrollModel.urlRoot = this.urlModel.get('commerce_api_url');
}
this.context = options.context || {};
+ this.grade = this.context.courseData.grades[this.model.get('course_run_key')];
+ this.grade = this.grade * 100;
+ this.collectionCourseStatus = this.context.collectionCourseStatus || '';
this.render();
this.listenTo(this.model, 'change', this.render);
},
@@ -59,6 +62,8 @@
this.enrollView = new CourseEnrollView({
$parentEl: this.$('.course-actions'),
model: this.model,
+ grade: this.grade,
+ collectionCourseStatus: this.collectionCourseStatus,
urlModel: this.urlModel,
enrollModel: this.enrollModel
});
diff --git a/lms/static/js/learner_dashboard/views/course_enroll_view.js b/lms/static/js/learner_dashboard/views/course_enroll_view.js
index 606b85e75f..00725d3b10 100644
--- a/lms/static/js/learner_dashboard/views/course_enroll_view.js
+++ b/lms/static/js/learner_dashboard/views/course_enroll_view.js
@@ -29,13 +29,18 @@
this.$parentEl = options.$parentEl;
this.enrollModel = options.enrollModel;
this.urlModel = options.urlModel;
+ this.grade = options.grade;
+ this.collectionCourseStatus = options.collectionCourseStatus;
this.render();
},
render: function() {
- var filledTemplate;
+ var filledTemplate,
+ context = this.model.toJSON();
if (this.$parentEl && this.enrollModel) {
- filledTemplate = this.tpl(this.model.toJSON());
+ context.grade = this.grade;
+ context.collectionCourseStatus = this.collectionCourseStatus;
+ filledTemplate = this.tpl(context);
HtmlUtils.setHtml(this.$el, filledTemplate);
HtmlUtils.setHtml(this.$parentEl, HtmlUtils.HTML(this.$el));
}
diff --git a/lms/static/js/learner_dashboard/views/program_details_view.js b/lms/static/js/learner_dashboard/views/program_details_view.js
index 7230720988..18f1c108ba 100644
--- a/lms/static/js/learner_dashboard/views/program_details_view.js
+++ b/lms/static/js/learner_dashboard/views/program_details_view.js
@@ -91,7 +91,7 @@
el: '.js-course-list-remaining',
childView: CourseCardView,
collection: this.remainingCourseCollection,
- context: this.options
+ context: $.extend(this.options, {collectionCourseStatus: 'remaining'})
}).render();
}
@@ -100,7 +100,7 @@
el: '.js-course-list-completed',
childView: CourseCardView,
collection: this.completedCourseCollection,
- context: this.options
+ context: $.extend(this.options, {collectionCourseStatus: 'completed'})
}).render();
}
@@ -110,7 +110,9 @@
el: '.js-course-list-in-progress',
childView: CourseCardView,
collection: this.inProgressCourseCollection,
- context: $.extend(this.options, {enrolled: gettext('Enrolled')})
+ context: $.extend(this.options,
+ {enrolled: gettext('Enrolled'), collectionCourseStatus: 'in_progress'}
+ )
}).render();
}
diff --git a/lms/static/js/spec/learner_dashboard/course_card_view_spec.js b/lms/static/js/spec/learner_dashboard/course_card_view_spec.js
index 425d67d3e5..2ef08531dd 100644
--- a/lms/static/js/spec/learner_dashboard/course_card_view_spec.js
+++ b/lms/static/js/spec/learner_dashboard/course_card_view_spec.js
@@ -13,14 +13,27 @@ define([
startDate = 'Feb 28, 2017',
endDate = 'May 30, 2017',
- setupView = function(data, isEnrolled) {
- var programData = $.extend({}, data);
+ setupView = function(data, isEnrolled, collectionCourseStatus) {
+ var programData = $.extend({}, data),
+ context = {
+ courseData: {
+ grades: {
+ 'course-v1:WageningenX+FFESx+1T2017': 0.8
+ }
+ },
+ collectionCourseStatus: collectionCourseStatus
+ };
+
+ if (typeof collectionCourseStatus === 'undefined') {
+ context.collectionCourseStatus = 'completed';
+ }
programData.course_runs[0].is_enrolled = isEnrolled;
setFixtures('
');
courseCardModel = new CourseCardModel(programData);
view = new CourseCardView({
- model: courseCardModel
+ model: courseCardModel,
+ context: context
});
},
@@ -84,6 +97,18 @@ define([
validateCourseInfoDisplay();
});
+ it('should render final grade if course is completed', function() {
+ view.remove();
+ setupView(course, true);
+ expect(view.$('.grade-display').text()).toEqual('80%');
+ });
+
+ it('should not render final grade if course has not been completed', function() {
+ view.remove();
+ setupView(course, true, 'in_progress');
+ expect(view.$('.final-grade').length).toEqual(0);
+ });
+
it('should render the course card based on the data not enrolled', function() {
validateCourseInfoDisplay();
});
diff --git a/lms/static/js/spec/learner_dashboard/program_details_view_spec.js b/lms/static/js/spec/learner_dashboard/program_details_view_spec.js
index 57f0670504..290f7db864 100644
--- a/lms/static/js/spec/learner_dashboard/program_details_view_spec.js
+++ b/lms/static/js/spec/learner_dashboard/program_details_view_spec.js
@@ -462,7 +462,10 @@ define([
}
]
}
- ]
+ ],
+ grades: {
+ 'course-v1:Testx+DOGx002+1T2016': 0.9
+ }
},
urls: {
program_listing_url: '/dashboard/programs/',
diff --git a/lms/static/sass/views/_program-details.scss b/lms/static/sass/views/_program-details.scss
index 46da9634bd..b30b35006e 100644
--- a/lms/static/sass/views/_program-details.scss
+++ b/lms/static/sass/views/_program-details.scss
@@ -520,6 +520,19 @@
width: 100%;
}
+
+ .final-grade {
+ .grade-header {
+ color: palette(grayscale, base);
+ font-weight: bold;
+ }
+ .grade-display {
+ padding-right: 15px;
+ font-size: 1.5em;
+ color: palette(primary, accent);
+ }
+ }
+
.upgrade-message {
flex-wrap: wrap;
diff --git a/lms/templates/learner_dashboard/course_enroll.underscore b/lms/templates/learner_dashboard/course_enroll.underscore
index b6769ce007..5e6dd72d11 100644
--- a/lms/templates/learner_dashboard/course_enroll.underscore
+++ b/lms/templates/learner_dashboard/course_enroll.underscore
@@ -1,4 +1,9 @@
-<% if (is_enrolled && (typeof expired === 'undefined' || expired === false)) { %>
+<% if (is_enrolled && collectionCourseStatus === 'completed') { %>
+
+<% } else if (is_enrolled && (typeof expired === 'undefined' || expired === false)) { %>
<% if (is_course_ended) { %>
<%- gettext('View Archived Course') %>
diff --git a/openedx/core/djangoapps/programs/tests/factories.py b/openedx/core/djangoapps/programs/tests/factories.py
index eb81dda41a..280e05962f 100644
--- a/openedx/core/djangoapps/programs/tests/factories.py
+++ b/openedx/core/djangoapps/programs/tests/factories.py
@@ -12,3 +12,4 @@ class ProgressFactory(factory.Factory):
completed = 0
in_progress = 0
not_started = 0
+ grades = dict()
diff --git a/openedx/core/djangoapps/programs/tests/test_utils.py b/openedx/core/djangoapps/programs/tests/test_utils.py
index 4747764607..d602afcbb4 100644
--- a/openedx/core/djangoapps/programs/tests/test_utils.py
+++ b/openedx/core/djangoapps/programs/tests/test_utils.py
@@ -18,6 +18,7 @@ from course_modes.models import CourseMode
from lms.djangoapps.certificates.api import MODES
from lms.djangoapps.commerce.tests.test_utils import update_commerce_config
from lms.djangoapps.commerce.utils import EcommerceService
+from lms.djangoapps.grades.tests.utils import mock_passing_grade
from openedx.core.djangoapps.catalog.tests.factories import (
generate_course_run_key,
ProgramFactory,
@@ -138,7 +139,7 @@ class TestProgramProgressMeter(TestCase):
self.assertEqual(meter.engaged_programs, [program])
self._assert_progress(
meter,
- ProgressFactory(uuid=program['uuid'], in_progress=1)
+ ProgressFactory(uuid=program['uuid'], in_progress=1, grades={course_run_key: 0.0})
)
self.assertEqual(meter.completed_programs, [])
@@ -169,7 +170,8 @@ class TestProgramProgressMeter(TestCase):
uuid=program['uuid'],
completed=[],
in_progress=[program['courses'][0]],
- not_started=[]
+ not_started=[],
+ grades={course_run_key: 0.0},
)
]
@@ -205,7 +207,8 @@ class TestProgramProgressMeter(TestCase):
uuid=program['uuid'],
completed=[],
in_progress=[program['courses'][0]],
- not_started=[]
+ not_started=[],
+ grades={course_run_key: 0.0},
)
]
@@ -246,7 +249,8 @@ class TestProgramProgressMeter(TestCase):
uuid=program['uuid'],
completed=0,
in_progress=1 if offset in [None, 1] else 0,
- not_started=1 if offset in [-1] else 0
+ not_started=1 if offset in [-1] else 0,
+ grades={course_run_key: 0.0},
)
]
@@ -285,9 +289,14 @@ class TestProgramProgressMeter(TestCase):
self._attach_detail_url(data)
programs = data[:2]
self.assertEqual(meter.engaged_programs, programs)
+
+ grades = {
+ newer_course_run_key: 0.0,
+ older_course_run_key: 0.0,
+ }
self._assert_progress(
meter,
- *(ProgressFactory(uuid=program['uuid'], in_progress=1) for program in programs)
+ *(ProgressFactory(uuid=program['uuid'], in_progress=1, grades=grades) for program in programs)
)
self.assertEqual(meter.completed_programs, [])
@@ -330,9 +339,15 @@ class TestProgramProgressMeter(TestCase):
self._attach_detail_url(data)
programs = data[:3]
self.assertEqual(meter.engaged_programs, programs)
+
+ grades = {
+ solo_course_run_key: 0.0,
+ shared_course_run_key: 0.0,
+ }
+
self._assert_progress(
meter,
- *(ProgressFactory(uuid=program['uuid'], in_progress=1) for program in programs)
+ *(ProgressFactory(uuid=program['uuid'], in_progress=1, grades=grades) for program in programs)
)
self.assertEqual(meter.completed_programs, [])
@@ -366,7 +381,7 @@ class TestProgramProgressMeter(TestCase):
program, program_uuid = data[0], data[0]['uuid']
self._assert_progress(
meter,
- ProgressFactory(uuid=program_uuid, in_progress=1, not_started=1)
+ ProgressFactory(uuid=program_uuid, in_progress=1, not_started=1, grades={first_course_run_key: 0.0})
)
self.assertEqual(meter.completed_programs, [])
@@ -375,7 +390,14 @@ class TestProgramProgressMeter(TestCase):
meter = ProgramProgressMeter(self.site, self.user)
self._assert_progress(
meter,
- ProgressFactory(uuid=program_uuid, in_progress=2)
+ ProgressFactory(
+ uuid=program_uuid,
+ in_progress=2,
+ grades={
+ first_course_run_key: 0.0,
+ second_course_run_key: 0.0,
+ },
+ )
)
self.assertEqual(meter.completed_programs, [])
@@ -386,7 +408,15 @@ class TestProgramProgressMeter(TestCase):
meter = ProgramProgressMeter(self.site, self.user)
self._assert_progress(
meter,
- ProgressFactory(uuid=program_uuid, completed=1, in_progress=1)
+ ProgressFactory(
+ uuid=program_uuid,
+ completed=1,
+ in_progress=1,
+ grades={
+ first_course_run_key: 0.0,
+ second_course_run_key: 0.0,
+ }
+ )
)
self.assertEqual(meter.completed_programs, [])
@@ -398,7 +428,15 @@ class TestProgramProgressMeter(TestCase):
meter = ProgramProgressMeter(self.site, self.user)
self._assert_progress(
meter,
- ProgressFactory(uuid=program_uuid, completed=1, in_progress=1)
+ ProgressFactory(
+ uuid=program_uuid,
+ completed=1,
+ in_progress=1,
+ grades={
+ first_course_run_key: 0.0,
+ second_course_run_key: 0.0,
+ }
+ )
)
self.assertEqual(meter.completed_programs, [])
@@ -410,7 +448,14 @@ class TestProgramProgressMeter(TestCase):
meter = ProgramProgressMeter(self.site, self.user)
self._assert_progress(
meter,
- ProgressFactory(uuid=program_uuid, completed=2)
+ ProgressFactory(
+ uuid=program_uuid,
+ completed=2,
+ grades={
+ first_course_run_key: 0.0,
+ second_course_run_key: 0.0,
+ }
+ )
)
self.assertEqual(meter.completed_programs, [program_uuid])
@@ -443,7 +488,7 @@ class TestProgramProgressMeter(TestCase):
program, program_uuid = data[0], data[0]['uuid']
self._assert_progress(
meter,
- ProgressFactory(uuid=program_uuid, completed=1)
+ ProgressFactory(uuid=program_uuid, completed=1, grades={course_run_key: 0.0})
)
self.assertEqual(meter.completed_programs, [program_uuid])
@@ -549,6 +594,38 @@ class TestProgramProgressMeter(TestCase):
mock_completed_course_runs.return_value = [{'course_run_id': course_run_key, 'type': 'verified'}]
self.assertEqual(meter._is_course_complete(course), True)
+ def test_course_grade_results(self, mock_get_programs):
+ grade_percent = .8
+ with mock_passing_grade(percent=grade_percent):
+ course_run_key = generate_course_run_key()
+ data = [
+ ProgramFactory(
+ courses=[
+ CourseFactory(course_runs=[
+ CourseRunFactory(key=course_run_key),
+ ]),
+ ]
+ )
+ ]
+ mock_get_programs.return_value = data
+
+ self._create_enrollments(course_run_key)
+
+ meter = ProgramProgressMeter(self.site, self.user)
+
+ program = data[0]
+ expected = [
+ ProgressFactory(
+ uuid=program['uuid'],
+ completed=[],
+ in_progress=[program['courses'][0]],
+ not_started=[],
+ grades={course_run_key: grade_percent},
+ )
+ ]
+
+ self.assertEqual(meter.progress(count_only=False), expected)
+
def _create_course(self, course_price, course_run_count=1):
"""
diff --git a/openedx/core/djangoapps/programs/utils.py b/openedx/core/djangoapps/programs/utils.py
index 02b5bdbc95..15a4c1b01f 100644
--- a/openedx/core/djangoapps/programs/utils.py
+++ b/openedx/core/djangoapps/programs/utils.py
@@ -22,6 +22,8 @@ from course_modes.models import CourseMode
from lms.djangoapps.certificates import api as certificate_api
from lms.djangoapps.commerce.utils import EcommerceService
from lms.djangoapps.courseware.access import has_access
+from lms.djangoapps.courseware.courses import get_course_with_access
+from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from openedx.core.djangoapps.catalog.utils import get_programs
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
@@ -89,6 +91,8 @@ class ProgramProgressMeter(object):
# We can't use dict.keys() for this because the course run ids need to be ordered
self.course_run_ids.append(enrollment_id)
+ self.course_grade_factory = CourseGradeFactory()
+
if uuid:
self.programs = [get_programs(self.site, uuid=uuid)]
else:
@@ -216,11 +220,17 @@ class ProgramProgressMeter(object):
else:
not_started.append(course)
+ grades = {}
+ for run in self.course_run_ids:
+ grade = self.course_grade_factory.read(self.user, course_key=CourseKey.from_string(run))
+ grades[run] = grade.percent
+
progress.append({
'uuid': program_copy['uuid'],
'completed': len(completed) if count_only else completed,
'in_progress': len(in_progress) if count_only else in_progress,
'not_started': len(not_started) if count_only else not_started,
+ 'grades': grades,
})
return progress
@@ -325,7 +335,7 @@ class ProgramProgressMeter(object):
course_data = {
'course_run_id': unicode(certificate['course_key']),
- 'type': certificate_type
+ 'type': certificate_type,
}
if certificate_api.is_passing_status(certificate['status']):