From 0b90de1d94aeca1fddd7da66bd6151db4cc374fb Mon Sep 17 00:00:00 2001 From: Simon Chen Date: Sun, 1 Mar 2020 15:18:29 -0500 Subject: [PATCH] MST-157 PART-2 Complete the functionality of searching for user info on program enrollment. The search result will be displayed on support tool's frontend. --- .../program_enrollments/api/__init__.py | 5 +- .../program_enrollments/api/reading.py | 87 ++++++--- lms/djangoapps/support/serializers.py | 63 ++++++- .../jsx/program_enrollments/inspector.jsx | 27 +-- lms/djangoapps/support/tests/test_views.py | 174 +++++++++++++----- .../support/views/program_enrollments.py | 134 +++++++------- .../program_enrollments_inspector.html | 1 - 7 files changed, 341 insertions(+), 150 deletions(-) diff --git a/lms/djangoapps/program_enrollments/api/__init__.py b/lms/djangoapps/program_enrollments/api/__init__.py index 3f76108502..5e170092b9 100644 --- a/lms/djangoapps/program_enrollments/api/__init__.py +++ b/lms/djangoapps/program_enrollments/api/__init__.py @@ -26,8 +26,9 @@ from .reading import ( get_program_enrollment, get_provider_slug, get_saml_provider_for_organization, - get_saml_provider_for_program, - get_users_by_external_keys + get_org_key_for_program, + get_users_by_external_keys, + get_users_by_external_keys_and_org_key ) from .writing import ( change_program_course_enrollment_status, diff --git a/lms/djangoapps/program_enrollments/api/reading.py b/lms/djangoapps/program_enrollments/api/reading.py index 67b646de82..5abbc7c65d 100644 --- a/lms/djangoapps/program_enrollments/api/reading.py +++ b/lms/djangoapps/program_enrollments/api/reading.py @@ -342,6 +342,44 @@ def _remove_none_values(dictionary): } +def get_users_by_external_keys_and_org_key(external_user_keys, org_key): + """ + Given an organization_key and a set of external keys, + return a dict from external user keys to Users. + + Args: + external_user_keys (sequence[str]): + external user keys used by the program creator's IdP. + org_key (str): + The organization short name of which the external_user_key belongs to + + Returns: dict[str: User|None] + A dict mapping external user keys to Users. + If an external user key is not registered, then None is returned instead + of a User for that key. + + Raises: + BadOrganizationShortNameException + ProviderDoesNotExistsException + ProviderConfigurationException + """ + saml_provider = get_saml_provider_by_org_key(org_key) + social_auth_uids = { + saml_provider.get_social_auth_uid(external_user_key) + for external_user_key in external_user_keys + } + social_auths = UserSocialAuth.objects.filter(uid__in=social_auth_uids) + found_users_by_external_keys = { + saml_provider.get_remote_id_from_social_auth(social_auth): social_auth.user + for social_auth in social_auths + } + # Default all external keys to None, because external keys + # without a User will not appear in `found_users_by_external_keys`. + users_by_external_keys = {key: None for key in external_user_keys} + users_by_external_keys.update(found_users_by_external_keys) + return users_by_external_keys + + def get_users_by_external_keys(program_uuid, external_user_keys): """ Given a program and a set of external keys, @@ -365,21 +403,8 @@ def get_users_by_external_keys(program_uuid, external_user_keys): ProviderDoesNotExistsException ProviderConfigurationException """ - saml_provider = get_saml_provider_for_program(program_uuid) - social_auth_uids = { - saml_provider.get_social_auth_uid(external_user_key) - for external_user_key in external_user_keys - } - social_auths = UserSocialAuth.objects.filter(uid__in=social_auth_uids) - found_users_by_external_keys = { - saml_provider.get_remote_id_from_social_auth(social_auth): social_auth.user - for social_auth in social_auths - } - # Default all external keys to None, because external keys - # without a User will not appear in `found_users_by_external_keys`. - users_by_external_keys = {key: None for key in external_user_keys} - users_by_external_keys.update(found_users_by_external_keys) - return users_by_external_keys + org_key = get_org_key_for_program(program_uuid) + return get_users_by_external_keys_and_org_key(external_user_keys, org_key) def get_external_key_by_user_and_course(user, course_key): @@ -409,20 +434,38 @@ def get_external_key_by_user_and_course(user, course_key): return relevant_pce.program_enrollment.external_user_key -def get_saml_provider_for_program(program_uuid): +def get_saml_provider_by_org_key(org_key): """ - Return currently configured SAML provider for the Organization + Returns the SAML provider associated with the provided org_key + + Arguments: + org_key (str) + + Returns: SAMLProvider + + Raises: + BadOrganizationShortNameException + """ + try: + organization = Organization.objects.get(short_name=org_key) + except Organization.DoesNotExist: + raise BadOrganizationShortNameException(org_key) + return get_saml_provider_for_organization(organization) + + +def get_org_key_for_program(program_uuid): + """ + Return the key of the first Organization administering the given program. Arguments: program_uuid (UUID|str) - Returns: SAMLProvider + Returns: org_key (str) Raises: ProgramDoesNotExistException ProgramHasNoAuthoringOrganizationException - BadOrganizationShortNameException """ program = get_programs(uuid=program_uuid) if program is None: @@ -431,11 +474,7 @@ def get_saml_provider_for_program(program_uuid): org_key = authoring_orgs[0].get('key') if authoring_orgs else None if not org_key: raise ProgramHasNoAuthoringOrganizationException(program_uuid) - try: - organization = Organization.objects.get(short_name=org_key) - except Organization.DoesNotExist: - raise BadOrganizationShortNameException(org_key) - return get_saml_provider_for_organization(organization) + return org_key def get_saml_provider_for_organization(organization): diff --git a/lms/djangoapps/support/serializers.py b/lms/djangoapps/support/serializers.py index 0dfff10191..d4099ef181 100644 --- a/lms/djangoapps/support/serializers.py +++ b/lms/djangoapps/support/serializers.py @@ -5,7 +5,14 @@ Serializers for use in the support app. from rest_framework import serializers -from student.models import ManualEnrollmentAudit +from student.models import CourseEnrollment, ManualEnrollmentAudit +from lms.djangoapps.program_enrollments.models import ( + ProgramEnrollment, + ProgramCourseEnrollment, +) + +DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S' +# pylint: disable=abstract-method class ManualEnrollmentSerializer(serializers.ModelSerializer): @@ -15,3 +22,57 @@ class ManualEnrollmentSerializer(serializers.ModelSerializer): class Meta(object): model = ManualEnrollmentAudit fields = ('enrolled_by', 'time_stamp', 'reason') + + +class CourseEnrollmentSerializer(serializers.Serializer): + """ Serializers a student_courseenrollment model object """ + course_id = serializers.CharField() + is_active = serializers.BooleanField() + mode = serializers.CharField() + + class Meta(object): + model = CourseEnrollment + + +class ProgramCourseEnrollmentSerializer(serializers.Serializer): + """ Serializes a Program Course Enrollment model object """ + created = serializers.DateTimeField(format=DATETIME_FORMAT) + modified = serializers.DateTimeField(format=DATETIME_FORMAT) + status = serializers.CharField() + course_key = serializers.CharField() + course_enrollment = CourseEnrollmentSerializer() + + class Meta(object): + model = ProgramCourseEnrollment + + +class ProgramEnrollmentSerializer(serializers.Serializer): + """ Serializes a Program Enrollment Model object """ + created = serializers.DateTimeField(format=DATETIME_FORMAT) + modified = serializers.DateTimeField(format=DATETIME_FORMAT) + external_user_key = serializers.CharField() + status = serializers.CharField() + program_uuid = serializers.UUIDField() + program_course_enrollments = ProgramCourseEnrollmentSerializer(many=True) + + class Meta(object): + model = ProgramEnrollment + + +def serialize_user_info(user, user_social_auth=None): + """ + Helper method to serialize resulting in user_info_object + based on passed in django models + """ + user_info = { + 'username': user.username, + 'email': user.email, + } + if user_social_auth: + _, external_key = user_social_auth.uid.split(':', 1) + user_info['external_user_key'] = external_key + user_info['sso'] = { + 'uid': user_social_auth.uid, + 'provider': user_social_auth.provider + } + return user_info diff --git a/lms/djangoapps/support/static/support/jsx/program_enrollments/inspector.jsx b/lms/djangoapps/support/static/support/jsx/program_enrollments/inspector.jsx index 8467de0dd2..dde601b502 100644 --- a/lms/djangoapps/support/static/support/jsx/program_enrollments/inspector.jsx +++ b/lms/djangoapps/support/static/support/jsx/program_enrollments/inspector.jsx @@ -4,17 +4,14 @@ import { Button, InputText, StatusAlert, InputSelect } from '@edx/paragon'; export const ProgramEnrollmentsInspectorPage = props => (
- {props.successes.length > 0 && ( + {props.errors.map(errorItem => ( - - - )} + dismissible={false} + alertType="danger" + dialog={errorItem} /> - )} + ))}
( ); ProgramEnrollmentsInspectorPage.propTypes = { - successes: PropTypes.arrayOf(PropTypes.string), errors: PropTypes.arrayOf(PropTypes.string), - learnerInfo: PropTypes.string, + learnerInfo: PropTypes.shape({ + user: PropTypes.shape({ + external_user_key: PropTypes.string, + username: PropTypes.string, + }), + enrollments: PropTypes.arrayOf( + PropTypes.string, + ), + }), orgKeys: PropTypes.arrayOf(PropTypes.object), }; ProgramEnrollmentsInspectorPage.defaultProps = { - successes: [], errors: [], - learnerInfo: '', + learnerInfo: {}, orgKeys: [], }; diff --git a/lms/djangoapps/support/tests/test_views.py b/lms/djangoapps/support/tests/test_views.py index c6a529f72b..81dc7aaa81 100644 --- a/lms/djangoapps/support/tests/test_views.py +++ b/lms/djangoapps/support/tests/test_views.py @@ -25,7 +25,10 @@ from common.test.utils import disable_signal from course_modes.models import CourseMode from course_modes.tests.factories import CourseModeFactory from lms.djangoapps.program_enrollments.tests.factories import ProgramCourseEnrollmentFactory, ProgramEnrollmentFactory +from lms.djangoapps.support.serializers import ProgramEnrollmentSerializer from lms.djangoapps.verify_student.models import VerificationDeadline +from lms.djangoapps.verify_student.services import IDVerificationService +from lms.djangoapps.verify_student.tests.factories import SSOVerificationFactory from student.models import ENROLLED_TO_ENROLLED, CourseEnrollment, CourseEnrollmentAttribute, ManualEnrollmentAudit from student.roles import GlobalStaff, SupportStaffRole from student.tests.factories import CourseEnrollmentFactory, UserFactory @@ -545,6 +548,7 @@ class SupportViewLinkProgramEnrollmentsTests(SupportViewTestCase): assert render_call_dict['errors'] == [msg] +@ddt.ddt class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase): """ View tests for Program Enrollments Inspector @@ -603,10 +607,10 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase): user_social_auth = UserSocialAuth.objects.create( user=user, uid='{0}:{1}'.format(org_key, external_user_key), - provider=org_key + provider='tpa-saml' ) user_info['external_user_key'] = external_user_key - user_info['SSO'] = { + user_info['sso'] = { 'uid': user_social_auth.uid, 'provider': user_social_auth.provider } @@ -618,27 +622,14 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase): If the edx user is provided, it will try to SSO the user with the enrollments Return the expected info object that should be created based on the model setup """ - expected_enrollments = [] + program_enrollments = [] for program_uuid in program_uuids: - expected_enrollment = {} - expected_course_enrollment = {} course_enrollment = None program_enrollment = ProgramEnrollmentFactory.create( external_user_key=external_user_key, program_uuid=program_uuid, user=edx_user ) - expected_enrollment['program_enrollment'] = { - 'created': self._serialize_datetime( - program_enrollment.created - ), - 'modified': self._serialize_datetime( - program_enrollment.modified - ), - 'program_uuid': program_enrollment.program_uuid, - 'external_user_key': external_user_key, - 'status': program_enrollment.status - } for course_id in course_ids: if edx_user: @@ -648,11 +639,6 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase): mode=CourseMode.MASTERS, is_active=True ) - expected_course_enrollment = { - 'course_id': str(course_enrollment.course_id), - 'is_active': course_enrollment.is_active, - 'mode': course_enrollment.mode, - } program_course_enrollment = ProgramCourseEnrollmentFactory.create( program_enrollment=program_enrollment, @@ -660,26 +646,22 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase): course_enrollment=course_enrollment, status='active', ) - expected_program_course_enrollment = { - 'created': self._serialize_datetime( - program_course_enrollment.created - ), - 'modified': self._serialize_datetime( - program_course_enrollment.modified - ), - 'status': program_course_enrollment.status, - 'course_key': str(program_course_enrollment.course_key), - } - if expected_course_enrollment: - expected_program_course_enrollment['course_enrollment'] = expected_course_enrollment - expected_enrollment.setdefault('program_course_enrollments', []).append( - expected_program_course_enrollment - ) + program_enrollments.append(program_enrollment) - expected_enrollments.append(expected_enrollment) + serialized = ProgramEnrollmentSerializer(program_enrollments, many=True) + return serialized.data - return expected_enrollments + def _construct_id_verification(self, user): + """ + Helper function to create the SSO verified record for the user + so that the user is ID Verified + """ + SSOVerificationFactory( + identity_provider_slug=self.org_key_list[0], + user=user, + ) + return IDVerificationService.user_status(user) @patch_render def test_search_username_well_connected_user(self, mocked_render): @@ -688,6 +670,7 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase): self.org_key_list[0], self.external_user_key ) + id_verified = self._construct_id_verification(created_user) expected_enrollments = self._construct_enrollments( [self.program_uuid], [self.course.id], @@ -699,7 +682,8 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase): }) expected_info = { 'user': expected_user_info, - 'enrollments': expected_enrollments + 'enrollments': expected_enrollments, + 'id_verification': id_verified } render_call_dict = mocked_render.call_args[0][1] @@ -712,7 +696,8 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase): 'edx_user': created_user.email }) expected_info = { - 'user': expected_user_info + 'user': expected_user_info, + 'id_verification': IDVerificationService.user_status(created_user) } render_call_dict = mocked_render.call_args[0][1] @@ -729,7 +714,8 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase): 'edx_user': created_user.email }) expected_info = { - 'user': expected_user_info + 'user': expected_user_info, + 'id_verification': IDVerificationService.user_status(created_user), } render_call_dict = mocked_render.call_args[0][1] @@ -753,7 +739,8 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase): }) expected_info = { 'user': expected_user_info, - 'enrollments': expected_enrollments + 'enrollments': expected_enrollments, + 'id_verification': IDVerificationService.user_status(created_user), } render_call_dict = mocked_render.call_args[0][1] @@ -774,7 +761,110 @@ class ProgramEnrollmentsInspectorViewTests(SupportViewTestCase): }) expected_info = { 'user': expected_user_info, + 'id_verification': IDVerificationService.user_status(created_user), } render_call_dict = mocked_render.call_args[0][1] assert expected_info == render_call_dict['learner_program_enrollments'] + + @patch_render + def test_search_username_user_id_verified(self, mocked_render): + created_user, expected_user_info = self._construct_user( + 'user_not_connected' + ) + id_verified = self._construct_id_verification(created_user) + expected_info = { + 'user': expected_user_info, + 'id_verification': id_verified + } + + self.client.get(self.url, data={ + 'edx_user': created_user.email + }) + + render_call_dict = mocked_render.call_args[0][1] + assert expected_info == render_call_dict['learner_program_enrollments'] + + @patch_render + def test_search_external_key_well_connected(self, mocked_render): + created_user, expected_user_info = self._construct_user( + 'test_user_connected', + self.org_key_list[0], + self.external_user_key + ) + expected_enrollments = self._construct_enrollments( + [self.program_uuid], + [self.course.id], + self.external_user_key, + created_user + ) + 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] + }) + expected_info = { + 'user': expected_user_info, + 'enrollments': expected_enrollments, + 'id_verification': id_verified, + } + + render_call_dict = mocked_render.call_args[0][1] + assert expected_info == render_call_dict['learner_program_enrollments'] + + @ddt.data( + ('aabbcc', ''), + ('', 'test_org') + ) + @ddt.unpack + @patch_render + def test_search_external_key_no_idp(self, user_key_input, idp_input, mocked_render): + self.client.get(self.url, data={ + 'external_user_key': user_key_input, + 'IdPSelect': idp_input, + }) + + expected_errors = [ + "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'] + + @patch_render + def test_search_external_user_not_connected(self, mocked_render): + expected_enrollments = self._construct_enrollments( + [self.program_uuid], + [self.course.id], + self.external_user_key, + ) + self.client.get(self.url, data={ + 'external_user_key': self.external_user_key, + 'IdPSelect': self.org_key_list[0] + }) + expected_info = { + 'enrollments': expected_enrollments + } + + render_call_dict = mocked_render.call_args[0][1] + assert expected_info == render_call_dict['learner_program_enrollments'] + + @patch_render + def test_search_external_user_not_in_system(self, mocked_render): + external_user_key = 'not_in_system' + self.client.get(self.url, data={ + 'external_user_key': external_user_key, + 'IdPSelect': self.org_key_list[0], + }) + + expected_errors = [ + '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'] diff --git a/lms/djangoapps/support/views/program_enrollments.py b/lms/djangoapps/support/views/program_enrollments.py index 5aa9a649f4..acf26fd8d3 100644 --- a/lms/djangoapps/support/views/program_enrollments.py +++ b/lms/djangoapps/support/views/program_enrollments.py @@ -15,9 +15,20 @@ from social_django.models import UserSocialAuth from edxmako.shortcuts import render_to_response from lms.djangoapps.program_enrollments.api import ( fetch_program_enrollments_by_student, + get_users_by_external_keys_and_org_key, link_program_enrollments ) +from lms.djangoapps.program_enrollments.exceptions import ( + BadOrganizationShortNameException, + ProviderConfigurationException, + ProviderDoesNotExistException +) from lms.djangoapps.support.decorators import require_support_permission +from lms.djangoapps.support.serializers import ( + ProgramEnrollmentSerializer, + serialize_user_info +) +from lms.djangoapps.verify_student.services import IDVerificationService from third_party_auth.models import SAMLProviderConfig TEMPLATE_PATH = 'support/link_program_enrollments.html' @@ -133,17 +144,27 @@ class ProgramEnrollmentsInspectorView(View): if error: errors.append(error) elif org_key and external_user_key: - learner_program_enrollments = {} - elif not external_user_key and org_key: + learner_program_enrollments = self._get_external_user_info( + external_user_key, + org_key + ) + if not learner_program_enrollments: + errors.append( + 'No user found for external key {} for institution {}'.format( + external_user_key, org_key + ) + ) + else: errors.append( - 'You must provide either the edX username or email, or the ' - 'Learner Account Provider and External Key pair to do search!' + "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!" ) return render_to_response( self.CONSOLE_TEMPLATE_PATH, { - 'successes': [], 'errors': errors, 'learner_program_enrollments': learner_program_enrollments, 'org_keys': self._get_org_keys_with_idp(), @@ -168,32 +189,59 @@ class ProgramEnrollmentsInspectorView(View): and program_enrollments_info. If we cannot identify the user, return empty object and error. """ - user_info = {} - external_key = None try: user = User.objects.get(Q(username=username_or_email) | Q(email=username_or_email)) - user_info['username'] = user.username - user_info['email'] = user.email + user_social_auth = None try: user_social_auth = UserSocialAuth.objects.get(user=user) - _, external_key = user_social_auth.uid.split(':', 1) - user_info['external_user_key'] = external_key - user_info['SSO'] = { - 'uid': user_social_auth.uid, - 'provider': user_social_auth.provider - } except UserSocialAuth.DoesNotExist: pass - + user_info = serialize_user_info(user, user_social_auth) enrollments = self._get_enrollments(user=user) result = {'user': user_info} if enrollments: result['enrollments'] = enrollments + result['id_verification'] = IDVerificationService.user_status(user) return result, '' 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): + """ + Provided the external_user_key and org_key, return edx account info + and program_enrollments_info if any. If we cannot identify the data, + return empty object. + """ + found_user = None + result = {} + try: + users_by_key = get_users_by_external_keys_and_org_key( + [external_user_key], + org_key + ) + found_user = users_by_key.get(external_user_key) + except ( + BadOrganizationShortNameException, + ProviderConfigurationException, + ProviderDoesNotExistException + ): + # 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 + return result + def _get_enrollments(self, user=None, external_user_key=None): """ With the user or external_user_key passed in, @@ -204,55 +252,5 @@ class ProgramEnrollmentsInspectorView(View): user=user, external_user_key=external_user_key ).prefetch_related('program_course_enrollments') - - enrollments_by_program_uuid = {} - for program_enrollment in program_enrollments: - serialized_program_enrollment = self._serialize_program_enrollment(program_enrollment) - enrollment_item = { - 'program_enrollment': serialized_program_enrollment - } - program_course_enrollments = program_enrollment.program_course_enrollments.all() - for program_course_enrollment in program_course_enrollments.select_related( - 'course_enrollment' - ): - serialized_program_course_enrollment = self._serialize_program_course_enrollment( - program_course_enrollment - ) - enrollment_item.setdefault('program_course_enrollments', []).append( - serialized_program_course_enrollment - ) - enrollments_by_program_uuid[program_enrollment.program_uuid] = enrollment_item - - return list(enrollments_by_program_uuid.values()) - - def _serialize_program_enrollment(self, program_enrollment): - if not program_enrollment: - return {} - - return { - 'created': program_enrollment.created.strftime(DATETIME_FORMAT), - 'modified': program_enrollment.modified.strftime(DATETIME_FORMAT), - 'program_uuid': str(program_enrollment.program_uuid), - 'external_user_key': program_enrollment.external_user_key, - 'status': program_enrollment.status - } - - def _serialize_program_course_enrollment(self, program_course_enrollment): - """ - Return a dictionary of ProgramCourseEnrollment serialized - """ - if not program_course_enrollment: - return {} - - course_enrollment = program_course_enrollment.course_enrollment - return { - 'created': program_course_enrollment.created.strftime(DATETIME_FORMAT), - 'modified': program_course_enrollment.modified.strftime(DATETIME_FORMAT), - 'course_enrollment': { - 'course_id': str(course_enrollment.course_id), - 'is_active': course_enrollment.is_active, - 'mode': course_enrollment.mode, - }, - 'status': program_course_enrollment.status, - 'course_key': str(program_course_enrollment.course_key), - } + serialized = ProgramEnrollmentSerializer(program_enrollments, many=True) + return serialized.data diff --git a/lms/templates/support/program_enrollments_inspector.html b/lms/templates/support/program_enrollments_inspector.html index a6b5be932e..fe7c162909 100644 --- a/lms/templates/support/program_enrollments_inspector.html +++ b/lms/templates/support/program_enrollments_inspector.html @@ -26,7 +26,6 @@ from openedx.core.djangolib.js_utils import js_escaped_string component="ProgramEnrollmentsInspectorPage", id="entitlement-support-page", props={ - 'successes': successes, 'errors': errors, 'learnerInfo': learner_program_enrollments, 'orgKeys': org_keys