+/*
+To improve the UI here, we should move this tool to the support Micro-Frontend.
+This work will be groomed and covered by MST-180
+*/
+const renderUserSection = userObj => (
+
+
edX account Info
+
+
Username: {userObj.username}
+
Email: {userObj.email}
+ {userObj.external_user_key && (
+
+ External User Key
+ : {userObj.external_user_key}
+
+ )}
+ {userObj.sso_list ? (
+
+
List of Single Sign On Records:
+
+ {userObj.sso_list.map(sso => (
+ - {sso.uid}
+ ))}
+
+
+ ) : (
+
There is no Single Sign On record associated with this user!
+ )}
-
-
+
+
+);
-
+
+const renderVerificationSection = verificationStatus => (
+
+
ID Verification
+
+
Status: {verificationStatus.status}
+ {verificationStatus.error && (
+
+ Verification Error: {verificationStatus.error}
+
+ )}
+ {verificationStatus.verification_expiry && (
+
+ Verification Expiration Date
+ : {verificationStatus.verification_expiry}
+
+ )}
-
-
+
+
+);
+
+const renderEnrollmentsSection = enrollments => (
+
+
Program Enrollments
+ {enrollments.map(enrollment => (
+
+
Program {enrollment.program_uuid} Enrollment
+
Status: {enrollment.status}
+
Created: {enrollment.created}
+
Last updated: {enrollment.modified}
+
+ External User Key
+ : {enrollment.external_user_key}
+
+ {enrollment.program_course_enrollments && enrollment.program_course_enrollments.map(
+ programCourseEnrollment => (
+
+
Course {programCourseEnrollment.course_key}
+
+ Status
+ : {programCourseEnrollment.status}
+
+
+ Created
+ : {programCourseEnrollment.created}
+
+
+ Last updated
+ : {programCourseEnrollment.modified}
+
+ {programCourseEnrollment.course_enrollment && (
+
+
Linked course enrollment
+
Course ID
+ : {programCourseEnrollment.course_enrollment.course_id}
+
+
Is Active
+ : {String(programCourseEnrollment.course_enrollment.is_active)}
+
+
Mode / Track
+ : {programCourseEnrollment.course_enrollment.mode}
+
+
+ )}
+
+ ))}
+
+ ))}
+
+
+);
+
+const validateInputs = () => {
+ const inputEdxUser = self.document.getElementById('edx_user');
+ const inputExternalKey = self.document.getElementById('external_key');
+ const inputAlert = self.document.getElementById('input_alert');
+ if (inputEdxUser.value && inputExternalKey.value) {
+ inputAlert.removeAttribute('hidden');
+ self.button.disabled = true;
+ } else {
+ inputAlert.setAttribute('hidden', '');
+ self.button.disabled = false;
+ }
+};
+
+export const ProgramEnrollmentsInspectorPage = props => (
+
+ {JSON.stringify(props.learnerInfo) !== '{}' && (
Search Results
)}
+ {props.learnerInfo.user &&
+ renderUserSection(props.learnerInfo.user)}
+ {props.learnerInfo.id_verification &&
+ renderVerificationSection(props.learnerInfo.id_verification)}
+ {props.learnerInfo.enrollments &&
+ renderEnrollmentsSection(props.learnerInfo.enrollments)}
+
+
);
ProgramEnrollmentsInspectorPage.propTypes = {
- errors: PropTypes.arrayOf(PropTypes.string),
+ error: PropTypes.string,
learnerInfo: PropTypes.shape({
user: PropTypes.shape({
- external_user_key: PropTypes.string,
username: PropTypes.string,
+ email: PropTypes.email,
+ external_user_key: PropTypes.string,
+ sso_list: PropTypes.arrayOf(
+ PropTypes.shape({
+ uid: PropTypes.string,
+ }),
+ ),
+ }),
+ id_verification: PropTypes.shape({
+ status: PropTypes.string,
+ error: PropTypes.string,
+ verification_expiry: PropTypes.string,
}),
enrollments: PropTypes.arrayOf(
- PropTypes.string,
+ PropTypes.shape({
+ created: PropTypes.string,
+ modified: PropTypes.string,
+ program_uuid: PropTypes.string,
+ status: PropTypes.string,
+ external_user_key: PropTypes.string,
+ program_course_enrollments: PropTypes.arrayOf(
+ PropTypes.shape({
+ course_key: PropTypes.string,
+ created: PropTypes.string,
+ modified: PropTypes.string,
+ status: PropTypes.string,
+ course_enrollment: PropTypes.shape({
+ course_id: PropTypes.string,
+ is_active: PropTypes.bool,
+ mode: PropTypes.string,
+ }),
+ })),
+ }),
),
}),
- orgKeys: PropTypes.arrayOf(PropTypes.object),
+ orgKeys: PropTypes.arrayOf(PropTypes.string),
};
ProgramEnrollmentsInspectorPage.defaultProps = {
- errors: [],
+ error: '',
learnerInfo: {},
orgKeys: [],
};
diff --git a/lms/djangoapps/support/tests/test_views.py b/lms/djangoapps/support/tests/test_views.py
index 81dc7aaa81..30110f68c5 100644
--- a/lms/djangoapps/support/tests/test_views.py
+++ b/lms/djangoapps/support/tests/test_views.py
@@ -587,7 +587,9 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
def test_initial_rendering(self):
response = self.client.get(self.url)
content = six.text_type(response.content, encoding='utf-8')
- expected_organization_serialized = '"orgKeys": {}'.format(json.dumps(self.org_key_list))
+ expected_organization_serialized = '"orgKeys": {}'.format(
+ json.dumps(sorted(self.org_key_list))
+ )
assert response.status_code == 200
assert expected_organization_serialized in content
assert '"learnerInfo": {}' in content
@@ -609,11 +611,9 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
uid='{0}:{1}'.format(org_key, external_user_key),
provider='tpa-saml'
)
- user_info['external_user_key'] = external_user_key
- user_info['sso'] = {
- 'uid': user_social_auth.uid,
- 'provider': user_social_auth.provider
- }
+ user_info['sso_list'] = [{
+ 'uid': user_social_auth.uid
+ }]
return user, user_info
def _construct_enrollments(self, program_uuids, course_ids, external_user_key, edx_user=None):
@@ -678,7 +678,8 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
created_user
)
self.client.get(self.url, data={
- 'edx_user': created_user.username
+ 'edx_user': created_user.username,
+ 'org_key': self.org_key_list[0]
})
expected_info = {
'user': expected_user_info,
@@ -693,7 +694,8 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
def test_search_username_user_not_connected(self, mocked_render):
created_user, expected_user_info = self._construct_user('user_not_connected')
self.client.get(self.url, data={
- 'edx_user': created_user.email
+ 'edx_user': created_user.email,
+ 'org_key': self.org_key_list[0]
})
expected_info = {
'user': expected_user_info,
@@ -711,7 +713,8 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
self.external_user_key
)
self.client.get(self.url, data={
- 'edx_user': created_user.email
+ 'edx_user': created_user.email,
+ 'org_key': self.org_key_list[0]
})
expected_info = {
'user': expected_user_info,
@@ -735,7 +738,8 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
created_user,
)
self.client.get(self.url, data={
- 'edx_user': created_user.email
+ 'edx_user': created_user.email,
+ 'org_key': self.org_key_list[0]
})
expected_info = {
'user': expected_user_info,
@@ -757,7 +761,8 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
self.external_user_key,
)
self.client.get(self.url, data={
- 'edx_user': created_user.email
+ 'edx_user': created_user.email,
+ 'org_key': self.org_key_list[0]
})
expected_info = {
'user': expected_user_info,
@@ -779,7 +784,8 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
}
self.client.get(self.url, data={
- 'edx_user': created_user.email
+ 'edx_user': created_user.email,
+ 'org_key': self.org_key_list[0]
})
render_call_dict = mocked_render.call_args[0][1]
@@ -801,7 +807,7 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
id_verified = self._construct_id_verification(created_user)
self.client.get(self.url, data={
'external_user_key': self.external_user_key,
- 'IdPSelect': self.org_key_list[0]
+ 'org_key': self.org_key_list[0]
})
expected_info = {
'user': expected_user_info,
@@ -813,27 +819,27 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
assert expected_info == render_call_dict['learner_program_enrollments']
@ddt.data(
- ('aabbcc', ''),
- ('', 'test_org')
+ ('', 'test_org'),
+ ('bad_key', '')
)
@ddt.unpack
@patch_render
- def test_search_external_key_no_idp(self, user_key_input, idp_input, mocked_render):
+ def test_search_no_external_user_key(self, user_key, org_key, mocked_render):
self.client.get(self.url, data={
- 'external_user_key': user_key_input,
- 'IdPSelect': idp_input,
+ 'external_user_key': user_key,
+ 'org_key': org_key,
})
- expected_errors = [
+ expected_error = (
"To perform a search, you must provide either the student's "
"(a) edX username, "
"(b) email address associated with their edX account, or "
"(c) Identity-providing institution and external key!"
- ]
+ )
render_call_dict = mocked_render.call_args[0][1]
assert {} == render_call_dict['learner_program_enrollments']
- assert expected_errors == render_call_dict['errors']
+ assert expected_error == render_call_dict['error']
@patch_render
def test_search_external_user_not_connected(self, mocked_render):
@@ -844,9 +850,12 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
)
self.client.get(self.url, data={
'external_user_key': self.external_user_key,
- 'IdPSelect': self.org_key_list[0]
+ 'org_key': self.org_key_list[0]
})
expected_info = {
+ 'user': {
+ 'external_user_key': self.external_user_key,
+ },
'enrollments': expected_enrollments
}
@@ -858,13 +867,11 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase):
external_user_key = 'not_in_system'
self.client.get(self.url, data={
'external_user_key': external_user_key,
- 'IdPSelect': self.org_key_list[0],
+ 'org_key': self.org_key_list[0],
})
- expected_errors = [
- 'No user found for external key {} for institution {}'.format(
- external_user_key, self.org_key_list[0]
- )
- ]
+ expected_error = 'No user found for external key {} for institution {}'.format(
+ external_user_key, self.org_key_list[0]
+ )
render_call_dict = mocked_render.call_args[0][1]
- assert expected_errors == render_call_dict['errors']
+ assert expected_error == render_call_dict['error']
diff --git a/lms/djangoapps/support/views/index.py b/lms/djangoapps/support/views/index.py
index 002df54eb0..87a4d5e1c8 100644
--- a/lms/djangoapps/support/views/index.py
+++ b/lms/djangoapps/support/views/index.py
@@ -48,6 +48,11 @@ SUPPORT_INDEX_URLS = [
"name": _("Link Program Enrollments"),
"description": _("Link LMS users to program enrollments"),
},
+ {
+ "url": reverse_lazy("support:program_enrollments_inspector"),
+ "name": _("Program Enrollments Inspector Tool"),
+ "description": _("Find information related to a learner's program enrollments"),
+ },
]
diff --git a/lms/djangoapps/support/views/program_enrollments.py b/lms/djangoapps/support/views/program_enrollments.py
index acf26fd8d3..13424eedf4 100644
--- a/lms/djangoapps/support/views/program_enrollments.py
+++ b/lms/djangoapps/support/views/program_enrollments.py
@@ -134,28 +134,35 @@ class ProgramEnrollmentsInspectorView(View):
Search the data store for information about ProgramEnrollment and
SSO linkage with the user.
"""
- errors = []
+ search_error = ''
edx_username_or_email = request.GET.get('edx_user', '').strip()
- org_key = request.GET.get('IdPSelect', '').strip()
+ org_key = request.GET.get('org_key', '').strip()
external_user_key = request.GET.get('external_user_key', '').strip()
learner_program_enrollments = {}
+ saml_providers_with_org_key = self._get_org_keys_and_idps()
+ selected_provider = None
+ if org_key:
+ selected_provider = saml_providers_with_org_key.get(org_key)
if edx_username_or_email:
- learner_program_enrollments, error = self._get_account_info(edx_username_or_email)
- if error:
- errors.append(error)
+ learner_program_enrollments, search_error = self._get_account_info(
+ edx_username_or_email,
+ selected_provider,
+ )
elif org_key and external_user_key:
learner_program_enrollments = self._get_external_user_info(
external_user_key,
- org_key
+ org_key,
+ selected_provider,
)
if not learner_program_enrollments:
- errors.append(
- 'No user found for external key {} for institution {}'.format(
- external_user_key, org_key
- )
+ search_error = 'No user found for external key {} for institution {}'.format(
+ external_user_key, org_key
)
+ elif not org_key and not external_user_key:
+ # This is initial rendering state.
+ pass
else:
- errors.append(
+ search_error = (
"To perform a search, you must provide either the student's "
"(a) edX username, "
"(b) email address associated with their edX account, or "
@@ -165,38 +172,39 @@ class ProgramEnrollmentsInspectorView(View):
return render_to_response(
self.CONSOLE_TEMPLATE_PATH,
{
- 'errors': errors,
+ 'error': search_error,
'learner_program_enrollments': learner_program_enrollments,
- 'org_keys': self._get_org_keys_with_idp(),
+ 'org_keys': sorted(saml_providers_with_org_key.keys()),
}
)
- def _get_org_keys_with_idp(self):
+ def _get_org_keys_and_idps(self):
"""
- From our Third_party_auth models, return a list
- of organizations whose SAMLProviders are active and configured
+ From our Third_party_auth models, return a dictionary of
+ of organizations keys and their correspondingactive and configured SAMLProviders
"""
saml_providers = SAMLProviderConfig.objects.current_set().filter(
enabled=True,
organization__isnull=False
).select_related('organization')
- return [saml_provider.organization.short_name for saml_provider in saml_providers]
+ return {
+ saml_provider.organization.short_name: saml_provider for saml_provider in saml_providers
+ }
- def _get_account_info(self, username_or_email):
+ def _get_account_info(self, username_or_email, idp_provider=None):
"""
- Provided the edx account username or email, return edx account info
- and program_enrollments_info. If we cannot identify the user, return
- empty object and error.
+ Provided the edx account username or email, and the SAML provider selected,
+ return edx account info and program_enrollments_info.
+ If we cannot identify the user, return empty object and error.
"""
try:
user = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email))
- user_social_auth = None
- try:
- user_social_auth = UserSocialAuth.objects.get(user=user)
- except UserSocialAuth.DoesNotExist:
- pass
- user_info = serialize_user_info(user, user_social_auth)
+ user_social_auths = None
+ user_social_auths = UserSocialAuth.objects.filter(user=user)
+ if idp_provider:
+ user_social_auths = user_social_auths.filter(provider=idp_provider.backend_name)
+ user_info = serialize_user_info(user, user_social_auths)
enrollments = self._get_enrollments(user=user)
result = {'user': user_info}
if enrollments:
@@ -207,7 +215,7 @@ class ProgramEnrollmentsInspectorView(View):
except User.DoesNotExist:
return {}, 'Could not find edx account with {}'.format(username_or_email)
- def _get_external_user_info(self, external_user_key, org_key):
+ def _get_external_user_info(self, external_user_key, org_key, idp_provider=None):
"""
Provided the external_user_key and org_key, return edx account info
and program_enrollments_info if any. If we cannot identify the data,
@@ -229,17 +237,19 @@ class ProgramEnrollmentsInspectorView(View):
# We cannot identify edX user from external_user_key and org_key pair
pass
- if found_user:
- try:
- user_social_auth = UserSocialAuth.objects.get(user=found_user)
- except UserSocialAuth.DoesNotExist:
- user_social_auth = None
- user_info = serialize_user_info(found_user, user_social_auth)
- result['user'] = user_info
- result['id_verification'] = IDVerificationService.user_status(found_user)
enrollments = self._get_enrollments(external_user_key=external_user_key)
if enrollments:
result['enrollments'] = enrollments
+ if found_user:
+ user_social_auths = UserSocialAuth.objects.filter(user=found_user)
+ if idp_provider:
+ user_social_auths = user_social_auths.filter(provider=idp_provider.backend_name)
+ user_info = serialize_user_info(found_user, user_social_auths)
+ result['user'] = user_info
+ result['id_verification'] = IDVerificationService.user_status(found_user)
+ elif 'enrollments' in result:
+ result['user'] = {'external_user_key': external_user_key}
+
return result
def _get_enrollments(self, user=None, external_user_key=None):
diff --git a/lms/templates/support/program_enrollments_inspector.html b/lms/templates/support/program_enrollments_inspector.html
index fe7c162909..35d7f0ec5f 100644
--- a/lms/templates/support/program_enrollments_inspector.html
+++ b/lms/templates/support/program_enrollments_inspector.html
@@ -21,16 +21,15 @@ from openedx.core.djangolib.js_utils import js_escaped_string
<%block name="content">
- Program Enrollments Inspector
+ Program Enrollments Inspector
${static.renderReact(
component="ProgramEnrollmentsInspectorPage",
- id="entitlement-support-page",
+ id="program-enrollments-inspector-page",
props={
- 'errors': errors,
+ 'error': error,
'learnerInfo': learner_program_enrollments,
'orgKeys': org_keys
}
- )
- }
+ )}
%block>