Skip to content

Commit

Permalink
Merge pull request #808 from edx/bseverino/onboarding-enrollment-mode
Browse files Browse the repository at this point in the history
[MST-683] Add enrollment mode to onboarding status view
  • Loading branch information
bseverino authored Mar 8, 2021
2 parents 02f315b + dc43b41 commit 2a54962
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 24 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Change Log
Unreleased
~~~~~~~~~~

[3.7.8] - 2021-03-08
~~~~~~~~~~~~~~~~~~~~
* Add enrollment mode column to onboarding status panel on instructor dashboard

[3.7.7] - 2021-03-08
~~~~~~~~~~~~~~~~~~~~
* Add loading spinner for searching to onboarding attempt and special attempts sections on the
Expand Down
2 changes: 1 addition & 1 deletion edx_proctoring/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"""

# Be sure to update the version number in edx_proctoring/package.json
__version__ = '3.7.7'
__version__ = '3.7.8'

default_app_config = 'edx_proctoring.apps.EdxProctoringConfig' # pylint: disable=invalid-name
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ edx = edx || {};
(function(Backbone, $, _, gettext) {
'use strict';

var viewHelper, onboardingStatuses, onboardingStatusReadableFormat;
var viewHelper, onboardingStatuses, statusAndModeReadableFormat;
edx.instructor_dashboard = edx.instructor_dashboard || {};
edx.instructor_dashboard.proctoring = edx.instructor_dashboard.proctoring || {};
onboardingStatuses = [
Expand All @@ -16,7 +16,8 @@ edx = edx || {};
'rejected',
'error'
];
onboardingStatusReadableFormat = {
statusAndModeReadableFormat = {
// Onboarding statuses
not_started: gettext('Not Started'),
setup_started: gettext('Setup Started'),
onboarding_started: gettext('Onboarding Started'),
Expand All @@ -25,7 +26,15 @@ edx = edx || {};
submitted: gettext('Submitted'),
verified: gettext('Verified'),
rejected: gettext('Rejected'),
error: gettext('Error')
error: gettext('Error'),
// Enrollment modes (Note: 'verified' is both a status and enrollment mode)
audit: gettext('Audit'),
honor: gettext('Honor'),
professional: gettext('Professional'),
'no-id-professional': gettext('No ID Professional'),
credit: gettext('Credit'),
masters: gettext('Master\'s'),
'executive-education': gettext('Executive Education')
};
viewHelper = {
getDateFormat: function(date) {
Expand All @@ -35,12 +44,11 @@ edx = edx || {};
return '---';
}
},
getOnboardingStatus: function(status) {
if (onboardingStatuses.includes(status)) {
return onboardingStatusReadableFormat[status];
} else {
return status;
getReadableString: function(str) {
if (str in statusAndModeReadableFormat) {
return statusAndModeReadableFormat[str];
}
return str;
}
};
edx.instructor_dashboard.proctoring.ProctoredExamOnboardingView = Backbone.View.extend({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,25 @@ describe('ProctoredExamOnboardingView', function() {
results: [
{
username: 'testuser1',
enrollment_mode: 'verified',
status: 'not_started',
modified: null
},
{
username: 'testuser2',
enrollment_mode: 'verified',
status: 'verified',
modified: '2021-01-28T17:59:19.913336Z'
},
{
username: 'testuser3',
enrollment_mode: 'masters',
status: 'submitted',
modified: '2021-01-28T17:46:05.316349Z'
},
{
username: 'testuser4',
enrollment_mode: 'executive-education',
status: 'other_course_approved',
modified: '2021-01-27T17:46:05.316349Z'
}
Expand Down Expand Up @@ -108,7 +112,7 @@ describe('ProctoredExamOnboardingView', function() {
'<% if (filters.includes(status)) { %>checked="true"<% } %>>' +
'<label for="<%= status %>">' +
'<%- interpolate(gettext(" %(onboardingStatus)s "), ' +
'{ onboardingStatus: getOnboardingStatus(status) }, true) %>' +
'{ onboardingStatus: getReadableString(status) }, true) %>' +
'</label>' +
'</li>' +
'<% }); %>' +
Expand All @@ -122,6 +126,7 @@ describe('ProctoredExamOnboardingView', function() {
'<thead>' +
'<tr class="onboarding-status-headings">' +
'<th class="username-heading"><%- gettext("Username") %></th>' +
'<th class="enrollment-mode-heading"><%- gettext("Enrollment Mode") %></th>' +
'<th class="onboarding-status-heading"><%- gettext("Onboarding Status") %></th>' +
'<th class="last-updated-heading"><%- gettext("Last Modified") %> </th>' +
'</tr>' +
Expand All @@ -134,8 +139,12 @@ describe('ProctoredExamOnboardingView', function() {
'<%- interpolate(gettext(" %(username)s "), { username: item.username }, true) %>' +
'</td>' +
'<td>' +
'<%- interpolate(gettext(" %(enrollmentMode)s "), ' +
'{ enrollmentMode: getReadableString(item.enrollment_mode) }, true) %>' +
'</td>' +
'<td>' +
'<%- interpolate(gettext(" %(onboardingStatus)s "), ' +
'{ onboardingStatus: getOnboardingStatus(item.status) }, true) %>' +
'{ onboardingStatus: getReadableString(item.status) }, true) %>' +
'</td>' +
'<td><%= getDateFormat(item.modified) %></td>' +
'</tr>' +
Expand Down Expand Up @@ -190,12 +199,18 @@ describe('ProctoredExamOnboardingView', function() {

expect(this.proctored_exam_onboarding_view.$el.find('.onboarding-items').length)
.toEqual(4);
expect(this.proctored_exam_onboarding_view.$el.find('.onboarding-items').html())
expect(this.proctored_exam_onboarding_view.$el.find('.onboarding-items').first().html())
.toContain('testuser1');
expect(this.proctored_exam_onboarding_view.$el.find('.onboarding-items').html())
expect(this.proctored_exam_onboarding_view.$el.find('.onboarding-items').first().html())
.toContain('Verified');
expect(this.proctored_exam_onboarding_view.$el.find('.onboarding-items').first().html())
.toContain('Not Started');
expect(this.proctored_exam_onboarding_view.$el.find('.onboarding-items').html())
expect(this.proctored_exam_onboarding_view.$el.find('.onboarding-items').first().html())
.toContain('---');
expect(this.proctored_exam_onboarding_view.$el.find('.onboarding-items').last().html())
.toContain('testuser4');
expect(this.proctored_exam_onboarding_view.$el.find('.onboarding-items').last().html())
.toContain('Executive Education');
});

it('renders correctly with no data', function() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@
<input type="checkbox" id="<%= status %>" name="status-filters" value="<%= status %>"
<% if (filters.includes(status)) { %>checked="true"<% } %>>
<label for="<%= status %>">
<%- interpolate(gettext(" %(onboardingStatus)s "),
{ onboardingStatus: getOnboardingStatus(status) }, true) %>
<%- interpolate(gettext(" %(onboardingStatus)s "),
{ onboardingStatus: getReadableString(status) }, true) %>
</label>
</li>
<% }); %>
Expand All @@ -113,6 +113,7 @@
<thead>
<tr class="onboarding-status-headings">
<th class="username-heading"><%- gettext("Username") %></th>
<th class="enrollment-mode-heading"><%- gettext("Enrollment Mode") %></th>
<th class="onboarding-status-heading"><%- gettext("Onboarding Status") %></th>
<th class="last-updated-heading"><%- gettext("Last Modified") %> </th>
</tr>
Expand All @@ -125,8 +126,12 @@
<%- interpolate(gettext(" %(username)s "), { username: item.username }, true) %>
</td>
<td>
<%- interpolate(gettext(" %(onboardingStatus)s "),
{ onboardingStatus: getOnboardingStatus(item.status) }, true) %>
<%- interpolate(gettext(" %(enrollmentMode)s "),
{ enrollmentMode: getReadableString(item.enrollment_mode) }, true) %>
</td>
<td>
<%- interpolate(gettext(" %(onboardingStatus)s "),
{ onboardingStatus: getReadableString(item.status) }, true) %>
</td>
<td><%= getDateFormat(item.modified) %></td>
</tr>
Expand Down
5 changes: 4 additions & 1 deletion edx_proctoring/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2458,15 +2458,18 @@ def test_get_enrollments_can_take_proctored_exams(self):
enrollments = [
{
'user': 'user_1',
'mode': 'verified',
},
{
'user': 'user_2',
'mode': 'masters',
},
{
'user': 'user_3',
'mode': 'executive-education',
},
]
expected_enrollments = [enrollment['user'] for enrollment in enrollments]
expected_enrollments = [(enrollment['user'], enrollment['mode']) for enrollment in enrollments]

with patch(
'edx_proctoring.tests.test_services.MockEnrollmentsService.get_enrollments_can_take_proctored_exams',
Expand Down
7 changes: 5 additions & 2 deletions edx_proctoring/tests/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,15 +313,18 @@ class MockEnrollment:
"""
Mock Enrollment
"""
def __init__(self, user):
def __init__(self, user, mode):
self.user = user
self.mode = mode


class MockEnrollmentsService:
"""Mock Enrollments service"""
def __init__(self, enrollments):
"""Initialize mock enrollments"""
self.enrollments = [MockEnrollment(enrollment['user']) for enrollment in enrollments]
self.enrollments = (
[MockEnrollment(enrollment['user'], enrollment['mode']) for enrollment in enrollments]
)

def get_active_enrollments_by_course(self, course_id):
"""Returns mock enrollments"""
Expand Down
20 changes: 20 additions & 0 deletions edx_proctoring/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,15 +709,19 @@ def setUp(self):
self.learner_2 = User(username='user2', email='[email protected]')
self.learner_2.save()

self.enrollment_modes = ['verified', 'masters', 'executive-education']
enrollments = [
{
'user': self.user,
'mode': self.enrollment_modes[0],
},
{
'user': self.learner_1,
'mode': self.enrollment_modes[1],
},
{
'user': self.learner_2,
'mode': self.enrollment_modes[2],
},
]
set_runtime_service('enrollments', MockEnrollmentsService(enrollments))
Expand Down Expand Up @@ -778,16 +782,19 @@ def test_multiple_onboarding_exams(self):
'results': [
{
'username': self.user.username,
'enrollment_mode': self.enrollment_modes[0],
'status': InstructorDashboardOnboardingAttemptStatus.submitted,
'modified': serialized_onboarding_attempt['modified'] if serialized_onboarding_attempt else None,
},
{
'username': self.learner_1.username,
'enrollment_mode': self.enrollment_modes[1],
'status': InstructorDashboardOnboardingAttemptStatus.not_started,
'modified': None,
},
{
'username': self.learner_2.username,
'enrollment_mode': self.enrollment_modes[2],
'status': InstructorDashboardOnboardingAttemptStatus.not_started,
'modified': None,
}
Expand Down Expand Up @@ -891,13 +898,15 @@ def test_one_status_filter(self):
'results': [
{
'username': self.user.username,
'enrollment_mode': self.enrollment_modes[0],
'status': InstructorDashboardOnboardingAttemptStatus.setup_started,
'modified': (first_serialized_onboarding_attempt['modified']
if first_serialized_onboarding_attempt else None
)
},
{
'username': self.learner_1.username,
'enrollment_mode': self.enrollment_modes[1],
'status': InstructorDashboardOnboardingAttemptStatus.setup_started,
'modified': (second_serialized_onboarding_attempt['modified']
if second_serialized_onboarding_attempt else None
Expand Down Expand Up @@ -940,13 +949,15 @@ def test_multiple_status_filters(self):
'results': [
{
'username': self.user.username,
'enrollment_mode': self.enrollment_modes[0],
'status': InstructorDashboardOnboardingAttemptStatus.setup_started,
'modified': (first_serialized_onboarding_attempt['modified']
if first_serialized_onboarding_attempt else None
)
},
{
'username': self.learner_1.username,
'enrollment_mode': self.enrollment_modes[1],
'status': InstructorDashboardOnboardingAttemptStatus.verified,
'modified': (second_serialized_onboarding_attempt['modified']
if second_serialized_onboarding_attempt else None
Expand Down Expand Up @@ -998,16 +1009,19 @@ def test_returns_correct_onboarding_status(self, attempt_status, expected_onboar
'results': [
{
'username': self.user.username,
'enrollment_mode': self.enrollment_modes[0],
'status': expected_onboarding_status,
'modified': serialized_onboarding_attempt['modified'] if serialized_onboarding_attempt else None,
},
{
'username': self.learner_1.username,
'enrollment_mode': self.enrollment_modes[1],
'status': InstructorDashboardOnboardingAttemptStatus.not_started,
'modified': None,
},
{
'username': self.learner_2.username,
'enrollment_mode': self.enrollment_modes[2],
'status': InstructorDashboardOnboardingAttemptStatus.not_started,
'modified': None,
},
Expand Down Expand Up @@ -1073,16 +1087,19 @@ def test_multiple_exam_attempts(self):
'results': [
{
'username': self.user.username,
'enrollment_mode': self.enrollment_modes[0],
'status': InstructorDashboardOnboardingAttemptStatus.setup_started,
'modified': serialized_onboarding_attempt['modified'] if serialized_onboarding_attempt else None,
},
{
'username': self.learner_1.username,
'enrollment_mode': self.enrollment_modes[1],
'status': InstructorDashboardOnboardingAttemptStatus.not_started,
'modified': None,
},
{
'username': self.learner_2.username,
'enrollment_mode': self.enrollment_modes[2],
'status': InstructorDashboardOnboardingAttemptStatus.not_started,
'modified': None,
},
Expand Down Expand Up @@ -1164,16 +1181,19 @@ def test_other_course_verified(self):
'results': [
{
'username': self.user.username,
'enrollment_mode': self.enrollment_modes[0],
'status': InstructorDashboardOnboardingAttemptStatus.not_started,
'modified': None,
},
{
'username': self.learner_1.username,
'enrollment_mode': self.enrollment_modes[1],
'status': InstructorDashboardOnboardingAttemptStatus.other_course_approved,
'modified': serialized_onboarding_attempt_1.get('modified'),
},
{
'username': self.learner_2.username,
'enrollment_mode': self.enrollment_modes[2],
'status': InstructorDashboardOnboardingAttemptStatus.setup_started,
'modified': serialized_onboarding_attempt_2.get('modified'),
},
Expand Down
13 changes: 11 additions & 2 deletions edx_proctoring/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ class StudentOnboardingStatusByCourseView(ProctoredAPIView):
* results: a list of dictionaries, where each dictionary contains the following
information about a learner's onboarding status:
* username: the user's username
* enrollment_mode: the user's enrollment mode for the course
* status: the status of the user's onboarding attempt as should be displayed by
the Instructor Dashboard; it will be one of InstructorDashboardOnboardingAttemptStatus
* modified: the date and time the user last modified the onboarding exam attempt;
Expand Down Expand Up @@ -452,7 +453,12 @@ def get(self, request, course_id):

enrollments = get_enrollments_can_take_proctored_exams(course_id, text_search)

users = [enrollment.user for enrollment in enrollments]
users = []
enrollment_modes_by_user_id = {}
for enrollment in enrollments:
users.append(enrollment.user)
enrollment_modes_by_user_id[enrollment.user.id] = enrollment.mode

# get onboarding attempts for users for the course
onboarding_attempts = ProctoredExamStudentAttempt.objects.get_proctored_practice_attempts_by_course_id(
course_id,
Expand All @@ -473,7 +479,10 @@ def get(self, request, course_id):
user_attempt = onboarding_attempts_per_user.get(user.id, {})
other_verified_attempt = last_verified_attempt_dict.get(user.id)

data = {'username': user.username}
data = {
'username': user.username,
'enrollment_mode': enrollment_modes_by_user_id.get(user.id),
}

if not user_attempt and other_verified_attempt:
data['status'] = InstructorDashboardOnboardingAttemptStatus.other_course_approved
Expand Down
Loading

0 comments on commit 2a54962

Please sign in to comment.