From db2656ae98472ab6e99d060810c165d8b997316f Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Thu, 2 Jul 2020 16:00:10 -0400 Subject: [PATCH 01/13] Constrain edx-enterprise. This will allow for more controlled releases of enterprise code in edx-platform. --- requirements/constraints.txt | 5 +++++ requirements/edx/base.txt | 8 ++++---- requirements/edx/development.txt | 8 ++++---- requirements/edx/testing.txt | 8 ++++---- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index bfc99cf2fa..b1d2aef1c4 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -40,6 +40,11 @@ drf-yasg<1.17.1 # drf-jwt 1.15.0 contains a migration that breaks on MySQL: https://github.com/Styria-Digital/django-rest-framework-jwt/issues/40 drf-jwt==1.14.0 +# The team that owns this package will manually bump this package rather than having it pulled in automatically. +# This is to allow them to better control its deployment and to do it in a process that works better +# for them. +edx-enterprise==3.3.15 + # Upgrading to 2.12.0 breaks several test classes due to API changes, need to update our code accordingly factory-boy==2.8.1 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 67050546f3..c5deecfc47 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -99,7 +99,7 @@ edx-django-release-util==0.4.4 # via -r requirements/edx/base.in edx-django-sites-extensions==2.5.1 # via -r requirements/edx/base.in edx-django-utils==3.2.3 # via -r requirements/edx/base.in, django-config-models, edx-drf-extensions, edx-enterprise, edx-rest-api-client, edx-when edx-drf-extensions==6.1.0 # via -r requirements/edx/base.in, edx-completion, edx-enterprise, edx-organizations, edx-proctoring, edx-rbac, edx-when, edxval -edx-enterprise==3.3.15 # via -r requirements/edx/base.in +edx-enterprise==3.3.15 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in edx-i18n-tools==0.5.3 # via ora2 edx-milestones==0.3.0 # via -r requirements/edx/base.in edx-opaque-keys[django]==2.1.0 # via -r requirements/edx/paver.txt, edx-bulk-grades, edx-ccx-keys, edx-completion, edx-drf-extensions, edx-enterprise, edx-milestones, edx-organizations, edx-proctoring, edx-user-state-client, edx-when, xmodule @@ -145,7 +145,7 @@ lazy==1.4 # via -r requirements/edx/paver.txt, acid-xblock, lti- lepl==5.1.3 # via rfc6266-parser libsass==0.10.0 # via -r requirements/edx/paver.txt, ora2 loremipsum==1.0.5 # via ora2 -lti-consumer-xblock==2.0.1.1 # via -r requirements/edx/base.in +lti-consumer-xblock==2.0.2 # via -r requirements/edx/base.in lxml==4.5.0 # via -c requirements/edx/../constraints.txt, -r requirements/edx/../edx-sandbox/shared.txt, capa, edxval, lti-consumer-xblock, ora2, safe-lxml, xblock, xmlsec mailsnake==1.6.4 # via -r requirements/edx/base.in mako==1.1.3 # via -r requirements/edx/base.in, acid-xblock, lti-consumer-xblock, xblock-google-drive, xblock-utils @@ -179,7 +179,7 @@ polib==1.1.0 # via edx-i18n-tools psutil==1.2.1 # via -r requirements/edx/paver.txt, edx-django-utils py2neo==3.1.2 # via -r requirements/edx/base.in pycontracts==1.8.12 # via -r requirements/edx/base.in, edx-user-state-client -pycountry==19.8.18 # via -r requirements/edx/base.in +pycountry==20.7.2 # via -r requirements/edx/base.in pycparser==2.20 # via -r requirements/edx/../edx-sandbox/shared.txt, cffi pycryptodome==3.9.8 # via lti-consumer-xblock, pdfminer.six pycryptodomex==3.9.8 # via -r requirements/edx/base.in, edx-proctoring, pyjwkest @@ -228,7 +228,7 @@ sqlparse==0.3.1 # via -r requirements/edx/base.in, django staff-graded-xblock==0.8 # via -r requirements/edx/base.in stevedore==1.32.0 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in, -r requirements/edx/paver.txt, code-annotations, edx-ace, edx-enterprise, edx-opaque-keys super-csv==0.9.9 # via -r requirements/edx/base.in, edx-bulk-grades -sympy==1.6 # via symmath +sympy==1.6.1 # via symmath testfixtures==6.14.1 # via edx-enterprise text-unidecode==1.3 # via python-slugify tqdm==4.47.0 # via -r requirements/edx/../edx-sandbox/shared.txt, nltk diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index fab5c57778..6a253c75b4 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -111,7 +111,7 @@ edx-django-release-util==0.4.4 # via -r requirements/edx/testing.txt edx-django-sites-extensions==2.5.1 # via -r requirements/edx/testing.txt edx-django-utils==3.2.3 # via -r requirements/edx/testing.txt, django-config-models, edx-drf-extensions, edx-enterprise, edx-rest-api-client, edx-when edx-drf-extensions==6.1.0 # via -r requirements/edx/testing.txt, edx-completion, edx-enterprise, edx-organizations, edx-proctoring, edx-rbac, edx-when, edxval -edx-enterprise==3.3.15 # via -r requirements/edx/testing.txt +edx-enterprise==3.3.15 # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt edx-i18n-tools==0.5.3 # via -r requirements/edx/testing.txt, ora2 edx-lint==1.5.0 # via -r requirements/edx/testing.txt edx-milestones==0.3.0 # via -r requirements/edx/testing.txt @@ -176,7 +176,7 @@ lazy==1.4 # via -r requirements/edx/testing.txt, acid-xblock, bo lepl==5.1.3 # via -r requirements/edx/testing.txt, rfc6266-parser libsass==0.10.0 # via -r requirements/edx/testing.txt, ora2 loremipsum==1.0.5 # via -r requirements/edx/testing.txt, ora2 -lti-consumer-xblock==2.0.1.1 # via -r requirements/edx/testing.txt +lti-consumer-xblock==2.0.2 # via -r requirements/edx/testing.txt lxml==4.5.0 # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt, capa, edxval, lti-consumer-xblock, ora2, pyquery, safe-lxml, xblock, xmlsec m2r==0.2.1 # via sphinxcontrib-openapi mailsnake==1.6.4 # via -r requirements/edx/testing.txt @@ -219,7 +219,7 @@ py2neo==3.1.2 # via -r requirements/edx/testing.txt py==1.9.0 # via -r requirements/edx/testing.txt, pytest, tox pycodestyle==2.6.0 # via -r requirements/edx/testing.txt, flake8 pycontracts==1.8.12 # via -r requirements/edx/testing.txt, edx-user-state-client -pycountry==19.8.18 # via -r requirements/edx/testing.txt +pycountry==20.7.2 # via -r requirements/edx/testing.txt pycparser==2.20 # via -r requirements/edx/testing.txt, cffi pycryptodome==3.9.8 # via -r requirements/edx/testing.txt, lti-consumer-xblock, pdfminer.six pycryptodomex==3.9.8 # via -r requirements/edx/testing.txt, edx-proctoring, pyjwkest @@ -299,7 +299,7 @@ sqlparse==0.3.1 # via -r requirements/edx/testing.txt, django, django- staff-graded-xblock==0.8 # via -r requirements/edx/testing.txt stevedore==1.32.0 # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt, code-annotations, edx-ace, edx-enterprise, edx-opaque-keys super-csv==0.9.9 # via -r requirements/edx/testing.txt, edx-bulk-grades -sympy==1.6 # via -r requirements/edx/testing.txt, symmath +sympy==1.6.1 # via -r requirements/edx/testing.txt, symmath testfixtures==6.14.1 # via -r requirements/edx/testing.txt, edx-enterprise text-unidecode==1.3 # via -r requirements/edx/testing.txt, faker, python-slugify toml==0.10.1 # via -r requirements/edx/testing.txt, tox diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index c4e0d7ebe9..9dc3a8cc81 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -108,7 +108,7 @@ edx-django-release-util==0.4.4 # via -r requirements/edx/base.txt edx-django-sites-extensions==2.5.1 # via -r requirements/edx/base.txt edx-django-utils==3.2.3 # via -r requirements/edx/base.txt, django-config-models, edx-drf-extensions, edx-enterprise, edx-rest-api-client, edx-when edx-drf-extensions==6.1.0 # via -r requirements/edx/base.txt, edx-completion, edx-enterprise, edx-organizations, edx-proctoring, edx-rbac, edx-when, edxval -edx-enterprise==3.3.15 # via -r requirements/edx/base.txt +edx-enterprise==3.3.15 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt edx-i18n-tools==0.5.3 # via -r requirements/edx/base.txt, -r requirements/edx/testing.in, ora2 edx-lint==1.5.0 # via -r requirements/edx/testing.in edx-milestones==0.3.0 # via -r requirements/edx/base.txt @@ -170,7 +170,7 @@ lazy==1.4 # via -r requirements/edx/base.txt, acid-xblock, bok-c lepl==5.1.3 # via -r requirements/edx/base.txt, rfc6266-parser libsass==0.10.0 # via -r requirements/edx/base.txt, ora2 loremipsum==1.0.5 # via -r requirements/edx/base.txt, ora2 -lti-consumer-xblock==2.0.1.1 # via -r requirements/edx/base.txt +lti-consumer-xblock==2.0.2 # via -r requirements/edx/base.txt lxml==4.5.0 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt, capa, edxval, lti-consumer-xblock, ora2, pyquery, safe-lxml, xblock, xmlsec mailsnake==1.6.4 # via -r requirements/edx/base.txt mako==1.1.3 # via -r requirements/edx/base.txt, acid-xblock, lti-consumer-xblock, xblock-google-drive, xblock-utils @@ -210,7 +210,7 @@ py2neo==3.1.2 # via -r requirements/edx/base.txt py==1.9.0 # via pytest, tox pycodestyle==2.6.0 # via -r requirements/edx/testing.in, flake8 pycontracts==1.8.12 # via -r requirements/edx/base.txt, edx-user-state-client -pycountry==19.8.18 # via -r requirements/edx/base.txt +pycountry==20.7.2 # via -r requirements/edx/base.txt pycparser==2.20 # via -r requirements/edx/base.txt, cffi pycryptodome==3.9.8 # via -r requirements/edx/base.txt, lti-consumer-xblock, pdfminer.six pycryptodomex==3.9.8 # via -r requirements/edx/base.txt, edx-proctoring, pyjwkest @@ -278,7 +278,7 @@ sqlparse==0.3.1 # via -r requirements/edx/base.txt, django staff-graded-xblock==0.8 # via -r requirements/edx/base.txt stevedore==1.32.0 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt, code-annotations, edx-ace, edx-enterprise, edx-opaque-keys super-csv==0.9.9 # via -r requirements/edx/base.txt, edx-bulk-grades -sympy==1.6 # via -r requirements/edx/base.txt, symmath +sympy==1.6.1 # via -r requirements/edx/base.txt, symmath testfixtures==6.14.1 # via -r requirements/edx/base.txt, -r requirements/edx/testing.in, edx-enterprise text-unidecode==1.3 # via -r requirements/edx/base.txt, faker, python-slugify toml==0.10.1 # via tox From df3f8d4344f9b2171b1a19270b4aa6caf2984d5b Mon Sep 17 00:00:00 2001 From: Tim McCormack Date: Tue, 7 Jul 2020 09:25:33 -0400 Subject: [PATCH 02/13] Enable "Sign in with Apple" auth backend (ARCHBOM-1281) (#24368) --- lms/envs/production.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/envs/production.py b/lms/envs/production.py index e451166a43..21f5c494a8 100644 --- a/lms/envs/production.py +++ b/lms/envs/production.py @@ -619,6 +619,7 @@ if FEATURES.get('ENABLE_THIRD_PARTY_AUTH'): 'social_core.backends.linkedin.LinkedinOAuth2', 'social_core.backends.facebook.FacebookOAuth2', 'social_core.backends.azuread.AzureADOAuth2', + 'social_core.backends.apple.AppleIdAuth', 'third_party_auth.identityserver3.IdentityServer3', 'third_party_auth.saml.SAMLAuthBackend', 'third_party_auth.lti.LTIAuthBackend', From acb9111bebe417f7ab3874c2f11cbb9bc45e08b4 Mon Sep 17 00:00:00 2001 From: Alex Dusenbery Date: Thu, 2 Jul 2020 12:48:15 -0400 Subject: [PATCH 03/13] cleanup devstack.py to only toggle ENABLE_ENTERPRISE_INTEGRATION in one place. --- lms/envs/devstack.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py index 4bafcbea24..31d8bb2fe4 100644 --- a/lms/envs/devstack.py +++ b/lms/envs/devstack.py @@ -323,7 +323,12 @@ FEATURES.update({ 'AUTOMATIC_AUTH_FOR_TESTING': True, 'ENABLE_DISCUSSION_SERVICE': True, 'SHOW_HEADER_LANGUAGE_SELECTOR': True, - 'ENABLE_ENTERPRISE_INTEGRATION': False, + + # Enable enterprise integration by default. + # See https://github.com/edx/edx-enterprise/blob/master/docs/development.rst for + # more background on edx-enterprise. + # Toggle this off if you don't want anything to do with enterprise in devstack. + 'ENABLE_ENTERPRISE_INTEGRATION': True, }) ENABLE_MKTG_SITE = os.environ.get('ENABLE_MARKETING_SITE', False) @@ -362,14 +367,15 @@ CREDENTIALS_SERVICE_USERNAME = 'credentials_worker' COURSE_CATALOG_URL_ROOT = 'http://edx.devstack.discovery:18381' COURSE_CATALOG_API_URL = '{}/api/v1'.format(COURSE_CATALOG_URL_ROOT) -# Enable enterprise integration so that we can include enterprise System-wide -# role logic without having to manipulate private settings. -FEATURES['ENABLE_ENTERPRISE_INTEGRATION'] = True SYSTEM_WIDE_ROLE_CLASSES = os.environ.get("SYSTEM_WIDE_ROLE_CLASSES", SYSTEM_WIDE_ROLE_CLASSES) -SYSTEM_WIDE_ROLE_CLASSES.extend([ +SYSTEM_WIDE_ROLE_CLASSES.append( 'system_wide_roles.SystemWideRoleAssignment', - 'enterprise.SystemWideEnterpriseUserRoleAssignment', -]) +) + +if FEATURES.get('ENABLE_ENTERPRISE_INTEGRATION'): + SYSTEM_WIDE_ROLE_CLASSES.append( + 'enterprise.SystemWideEnterpriseUserRoleAssignment', + ) # List of enterprise customer uuids to exclude from transition to use of enterprise-catalog ENTERPRISE_CUSTOMERS_EXCLUDED_FROM_CATALOG = () From 8f64860b859e5cdede70668a9178108da0e72588 Mon Sep 17 00:00:00 2001 From: Christie Rice <8483753+crice100@users.noreply.github.com> Date: Wed, 10 Jun 2020 15:15:53 -0400 Subject: [PATCH 04/13] MICROBA-393 Add customized partner report headings --- .../user_api/accounts/serializers.py | 2 + .../accounts/tests/test_retirement_views.py | 90 +++++++++++++++--- .../djangoapps/user_api/accounts/views.py | 92 ++++++++++++++++--- 3 files changed, 157 insertions(+), 27 deletions(-) diff --git a/openedx/core/djangoapps/user_api/accounts/serializers.py b/openedx/core/djangoapps/user_api/accounts/serializers.py index 7f197cb8b6..1110bfad9c 100644 --- a/openedx/core/djangoapps/user_api/accounts/serializers.py +++ b/openedx/core/djangoapps/user_api/accounts/serializers.py @@ -475,10 +475,12 @@ class UserRetirementPartnerReportSerializer(serializers.Serializer): Perform serialization for the UserRetirementPartnerReportingStatus model """ user_id = serializers.IntegerField() + student_id = serializers.CharField(required=False) original_username = serializers.CharField() original_email = serializers.EmailField() original_name = serializers.CharField() orgs = serializers.ListField(child=serializers.CharField()) + orgs_config = serializers.ListField(required=False) created = serializers.DateTimeField() # Required overrides of abstract base class methods, but we don't use them diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py index b9e17df87c..17e75f3b53 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py @@ -46,6 +46,7 @@ from openedx.core.djangoapps.credit.models import ( CreditRequirement, CreditRequirementStatus ) +from openedx.core.djangoapps.external_user_ids.models import ExternalId, ExternalIdType from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory from openedx.core.djangoapps.user_api.accounts.views import AccountRetirementPartnerReportView @@ -287,7 +288,7 @@ class TestPartnerReportingCleanup(ModuleStoreTestCase): def create_partner_reporting_statuses(self, is_being_processed=True, num=2): """ - Creates and returnes the given number of test users and UserRetirementPartnerReportingStatuses + Creates and returns the given number of test users and UserRetirementPartnerReportingStatuses with the given is_being_processed value. """ statuses = [] @@ -482,6 +483,17 @@ class TestPartnerReportingList(ModuleStoreTestCase): """ Tests the partner reporting list endpoint """ + EXPECTED_MB_ORGS_CONFIG = [ + { + AccountRetirementPartnerReportView.ORGS_CONFIG_ORG_KEY: 'mb_coaching', + AccountRetirementPartnerReportView.ORGS_CONFIG_FIELD_HEADINGS_KEY: [ + AccountRetirementPartnerReportView.STUDENT_ID_KEY, + AccountRetirementPartnerReportView.ORIGINAL_EMAIL_KEY, + AccountRetirementPartnerReportView.ORIGINAL_NAME_KEY, + AccountRetirementPartnerReportView.DELETION_COMPLETED_KEY + ] + } + ] def setUp(self): super(TestPartnerReportingList, self).setUp() @@ -493,6 +505,7 @@ class TestPartnerReportingList(ModuleStoreTestCase): self.url = reverse('accounts_retirement_partner_report') self.maxDiff = None self.test_created_datetime = datetime.datetime(2018, 1, 1, tzinfo=pytz.UTC) + ExternalIdType.objects.get_or_create(name=ExternalIdType.MICROBACHELORS_COACHING) def get_user_dict(self, user, enrollments): """ @@ -517,9 +530,10 @@ class TestPartnerReportingList(ModuleStoreTestCase): their processing state to "is_being_processed". Returns a list of user dicts representing what we would expect back from the - endpoint for the given user / enrollment. + endpoint for the given user / enrollment, and a list of the users themselves. """ user_dicts = [] + users = [] courses = self.courses if courses is None else courses for _ in range(num): @@ -540,8 +554,9 @@ class TestPartnerReportingList(ModuleStoreTestCase): user_dicts.append( self.get_user_dict(user, enrollments) ) + users.append(user) - return user_dicts + return user_dicts, users def assert_status_and_user_list(self, expected_users, expected_status=status.HTTP_200_OK): """ @@ -571,21 +586,72 @@ class TestPartnerReportingList(ModuleStoreTestCase): """ Basic test to make sure that users in two different orgs are returned. """ - users = self.create_partner_reporting_statuses() - users += self.create_partner_reporting_statuses(courses=(self.course_awesome_org,)) + user_dicts, users = self.create_partner_reporting_statuses() + additional_dicts, additional_users = self.create_partner_reporting_statuses(courses=(self.course_awesome_org,)) + user_dicts += additional_dicts - self.assert_status_and_user_list(users) + self.assert_status_and_user_list(user_dicts) def test_success_multiple_statuses(self): """ Checks that only users in the correct is_being_processed state (False) are returned. """ - users = self.create_partner_reporting_statuses() + user_dicts, users = self.create_partner_reporting_statuses() # These should not come back self.create_partner_reporting_statuses(courses=(self.course_awesome_org,), is_being_processed=True) - self.assert_status_and_user_list(users) + self.assert_status_and_user_list(user_dicts) + + def test_success_mb_coaching(self): + """ + Check that MicroBachelors users who have consented to coaching have the proper info + included for the partner report. + """ + path = 'openedx.core.djangoapps.user_api.accounts.views.has_ever_consented_to_coaching' + with mock.patch(path, return_value=True) as mock_has_ever_consented: + user_dicts, users = self.create_partner_reporting_statuses(num=1) + external_id, created = ExternalId.add_new_user_id( + user=users[0], + type_name=ExternalIdType.MICROBACHELORS_COACHING + ) + + expected_user = user_dicts[0] + expected_users = [expected_user] + expected_user[AccountRetirementPartnerReportView.STUDENT_ID_KEY] = str(external_id.external_user_id) + expected_user[ + AccountRetirementPartnerReportView.ORGS_CONFIG_KEY] = TestPartnerReportingList.EXPECTED_MB_ORGS_CONFIG + + self.assert_status_and_user_list(expected_users) + mock_has_ever_consented.assert_called_once() + + def test_success_mb_coaching_no_external_id(self): + """ + Check that MicroBachelors users who have consented to coaching, but who do not have an external id, have the + proper info included for the partner report. + """ + path = 'openedx.core.djangoapps.user_api.accounts.views.has_ever_consented_to_coaching' + with mock.patch(path, return_value=True) as mock_has_ever_consented: + user_dicts, users = self.create_partner_reporting_statuses(num=1) + + self.assert_status_and_user_list(user_dicts) + mock_has_ever_consented.assert_called_once() + + def test_success_mb_coaching_no_consent(self): + """ + Check that MicroBachelors users who have not consented to coaching have the proper info + included for the partner report. + """ + path = 'openedx.core.djangoapps.user_api.accounts.views.has_ever_consented_to_coaching' + with mock.patch(path, return_value=False) as mock_has_ever_consented: + user_dicts, users = self.create_partner_reporting_statuses(num=1) + ExternalId.add_new_user_id( + user=users[0], + type_name=ExternalIdType.MICROBACHELORS_COACHING + ) + + self.assert_status_and_user_list(user_dicts) + mock_has_ever_consented.assert_called_once() def test_no_users(self): """ @@ -606,10 +672,10 @@ class TestPartnerReportingList(ModuleStoreTestCase): Checks that users are progressed to "is_being_processed" True upon being returned from this call. """ - users = self.create_partner_reporting_statuses() + user_dicts, users = self.create_partner_reporting_statuses() # First time through we should get the users - self.assert_status_and_user_list(users) + self.assert_status_and_user_list(user_dicts) # Second time they should be updated to is_being_processed=True self.assert_status_and_user_list([]) @@ -1105,7 +1171,7 @@ class TestAccountRetirementUpdate(RetirementTestCase): data = {'new_state': 'LOCKING_ACCOUNT', 'response': 'this should succeed'} self.update_and_assert_status(data) - # Refresh the retirment object and confirm the messages and state are correct + # Refresh the retirement object and confirm the messages and state are correct retirement = UserRetirementStatus.objects.get(id=self.retirement.id) self.assertEqual(retirement.current_state, RetirementState.objects.get(state_name='LOCKING_ACCOUNT')) self.assertEqual(retirement.last_state, RetirementState.objects.get(state_name='PENDING')) @@ -1127,7 +1193,7 @@ class TestAccountRetirementUpdate(RetirementTestCase): for update_data in fake_retire_process: self.update_and_assert_status(update_data) - # Refresh the retirment object and confirm the messages and state are correct + # Refresh the retirement object and confirm the messages and state are correct retirement = UserRetirementStatus.objects.get(id=self.retirement.id) self.assertEqual(retirement.current_state, RetirementState.objects.get(state_name='COMPLETE')) self.assertEqual(retirement.last_state, RetirementState.objects.get(state_name='CREDENTIALS_COMPLETE')) diff --git a/openedx/core/djangoapps/user_api/accounts/views.py b/openedx/core/djangoapps/user_api/accounts/views.py index c319af66d6..0719ebe217 100644 --- a/openedx/core/djangoapps/user_api/accounts/views.py +++ b/openedx/core/djangoapps/user_api/accounts/views.py @@ -44,6 +44,7 @@ from openedx.core.djangoapps.ace_common.template_context import get_base_templat from openedx.core.djangoapps.api_admin.models import ApiAccessRequest from openedx.core.djangoapps.course_groups.models import UnregisteredLearnerCohortAssignments from openedx.core.djangoapps.credit.models import CreditRequest, CreditRequirementStatus +from openedx.core.djangoapps.external_user_ids.models import ExternalId, ExternalIdType from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY from openedx.core.djangoapps.profile_images.images import remove_profile_images from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_names, set_has_profile_image @@ -82,6 +83,11 @@ from .permissions import CanDeactivateUser, CanReplaceUsername, CanRetireUser from .serializers import UserRetirementPartnerReportSerializer, UserRetirementStatusSerializer from .signals import USER_RETIRE_LMS_CRITICAL, USER_RETIRE_LMS_MISC, USER_RETIRE_MAILINGS +try: + from coaching.api import has_ever_consented_to_coaching +except ImportError: + has_ever_consented_to_coaching = None + log = logging.getLogger(__name__) USER_PROFILE_PII = { @@ -530,6 +536,14 @@ class AccountRetirementPartnerReportView(ViewSet): Provides API endpoints for managing partner reporting of retired users. """ + DELETION_COMPLETED_KEY = 'deletion_completed' + ORGS_CONFIG_KEY = 'orgs_config' + ORGS_CONFIG_ORG_KEY = 'org' + ORGS_CONFIG_FIELD_HEADINGS_KEY = 'field_headings' + ORIGINAL_EMAIL_KEY = 'original_email' + ORIGINAL_NAME_KEY = 'original_name' + STUDENT_ID_KEY = 'student_id' + authentication_classes = (JwtAuthentication,) permission_classes = (permissions.IsAuthenticated, CanRetireUser,) parser_classes = (JSONParser,) @@ -544,7 +558,7 @@ class AccountRetirementPartnerReportView(ViewSet): for enrollment in user.courseenrollment_set.all(): org = enrollment.course_id.org - # Org can concievably be blank or this bogus default value + # Org can conceivably be blank or this bogus default value if org and org != 'outdated_entry': orgs.add(org) try: @@ -569,17 +583,9 @@ class AccountRetirementPartnerReportView(ViewSet): is_being_processed=False ).order_by('id') - retirements = [ - { - 'user_id': retirement.user.pk, - 'original_username': retirement.original_username, - 'original_email': retirement.original_email, - 'original_name': retirement.original_name, - 'orgs': self._get_orgs_for_user(retirement.user), - 'created': retirement.created, - } - for retirement in retirement_statuses - ] + retirements = [] + for retirement_status in retirement_statuses: + retirements.append(self._get_retirement_for_partner_report(retirement_status)) serializer = UserRetirementPartnerReportSerializer(retirements, many=True) @@ -587,6 +593,62 @@ class AccountRetirementPartnerReportView(ViewSet): return Response(serializer.data) + def _get_retirement_for_partner_report(self, retirement_status): + """ + Get the retirement for this retirement_status. The retirement info will be included in the partner report. + """ + retirement = { + 'user_id': retirement_status.user.pk, + 'original_username': retirement_status.original_username, + AccountRetirementPartnerReportView.ORIGINAL_EMAIL_KEY: retirement_status.original_email, + AccountRetirementPartnerReportView.ORIGINAL_NAME_KEY: retirement_status.original_name, + 'orgs': self._get_orgs_for_user(retirement_status.user), + 'created': retirement_status.created, + } + + # Some orgs have a custom list of headings and content for the partner report. Add this, if applicable. + self._add_orgs_config_for_user(retirement, retirement_status.user) + + return retirement + + def _add_orgs_config_for_user(self, retirement, user): + """ + Check to see if the user's info was sent to any partners (orgs) that have a a custom list of headings and + content for the partner report. If so, add this. + """ + # See if the MicroBachelors coaching provider needs to be notified of this user's retirement + if has_ever_consented_to_coaching is not None and has_ever_consented_to_coaching(user): + # See if the user has a MicroBachelors external id. If not, they were never sent to the + # coaching provider. + external_ids = ExternalId.objects.filter( + user=user, + external_id_type__name=ExternalIdType.MICROBACHELORS_COACHING + ) + if external_ids.exists(): + # User has an external id. Add the additional info. + external_id = str(external_ids[0].external_user_id) + self._add_coaching_orgs_config(retirement, external_id) + + def _add_coaching_orgs_config(self, retirement, external_id): + """ + Add the orgs configuration for MicroBachelors coaching + """ + # Add the custom field headings + retirement[AccountRetirementPartnerReportView.ORGS_CONFIG_KEY] = [ + { + AccountRetirementPartnerReportView.ORGS_CONFIG_ORG_KEY: 'mb_coaching', + AccountRetirementPartnerReportView.ORGS_CONFIG_FIELD_HEADINGS_KEY: [ + AccountRetirementPartnerReportView.STUDENT_ID_KEY, + AccountRetirementPartnerReportView.ORIGINAL_EMAIL_KEY, + AccountRetirementPartnerReportView.ORIGINAL_NAME_KEY, + AccountRetirementPartnerReportView.DELETION_COMPLETED_KEY + ] + } + ] + + # Add the custom field value + retirement[AccountRetirementPartnerReportView.STUDENT_ID_KEY] = external_id + @request_requires_username def retirement_partner_status_create(self, request): """ @@ -707,7 +769,7 @@ class AccountRetirementStatusView(ViewSet): ) serializer = UserRetirementStatusSerializer(retirements, many=True) return Response(serializer.data) - # This should only occur on the int() converstion of cool_off_days at this point + # This should only occur on the int() conversion of cool_off_days at this point except ValueError: return Response('Invalid cool_off_days, should be integer.', status=status.HTTP_400_BAD_REQUEST) except KeyError as exc: @@ -1056,7 +1118,7 @@ class UsernameReplacementView(APIView): updates usernames across all services. DO NOT run this alone or users will not match across the system and things will be broken. - API will recieve a list of current usernames and their requested new + API will receive a list of current usernames and their requested new username. If their new username is taken, it will randomly assign a new username. This API will be called first, before calling the APIs in other services as this @@ -1166,7 +1228,7 @@ class UsernameReplacementView(APIView): """ Generates a unique username. If the desired username is available, that will be returned. - Otherwise it will generate unique suffixs to the desired username until it is an available username. + Otherwise it will generate unique suffixes to the desired username until it is an available username. """ new_username = desired_username # Keep checking usernames in case desired_username + random suffix is already taken From 31a525580d80c53d791ca0821e5ff844b17f8b3c Mon Sep 17 00:00:00 2001 From: Christie Rice <8483753+crice100@users.noreply.github.com> Date: Wed, 24 Jun 2020 12:26:43 -0400 Subject: [PATCH 05/13] Add sorting --- .../user_api/accounts/tests/test_retirement_views.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py index 17e75f3b53..ab44772a3b 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py @@ -576,9 +576,19 @@ class TestPartnerReportingList(ModuleStoreTestCase): # These sub-lists will fail assertCountEqual if they're out of order for expected_user in expected_users: expected_user['orgs'].sort() + if AccountRetirementPartnerReportView.ORGS_CONFIG_KEY in expected_user: + orgs_config = expected_user[AccountRetirementPartnerReportView.ORGS_CONFIG_KEY] + orgs_config.sort() + for config in orgs_config: + config[AccountRetirementPartnerReportView.ORGS_CONFIG_FIELD_HEADINGS_KEY].sort() for returned_user in returned_users: returned_user['orgs'].sort() + if AccountRetirementPartnerReportView.ORGS_CONFIG_KEY in returned_user: + orgs_config = returned_user[AccountRetirementPartnerReportView.ORGS_CONFIG_KEY] + orgs_config.sort() + for config in orgs_config: + config[AccountRetirementPartnerReportView.ORGS_CONFIG_FIELD_HEADINGS_KEY].sort() self.assertCountEqual(returned_users, expected_users) From 6026a98e11835819d35ee2fb2284b81822d8edea Mon Sep 17 00:00:00 2001 From: Aura Milena Alba <36944773+amalbas@users.noreply.github.com> Date: Tue, 7 Jul 2020 10:23:22 -0500 Subject: [PATCH 06/13] Remove pattern library in course_home.py (#24157) [BD-10] [DEPR-83][DEPR-81] Remove pattern library in course_home.py --- openedx/features/course_experience/views/course_home.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openedx/features/course_experience/views/course_home.py b/openedx/features/course_experience/views/course_home.py index 446fb22fa1..15fa55d803 100644 --- a/openedx/features/course_experience/views/course_home.py +++ b/openedx/features/course_experience/views/course_home.py @@ -84,6 +84,7 @@ class CourseHomeFragmentView(EdxFragmentView): """ A fragment to render the home page for a course. """ + _uses_pattern_library = False def _get_resume_course_info(self, request, course_id): """ @@ -258,7 +259,7 @@ class CourseHomeFragmentView(EdxFragmentView): 'update_message_fragment': update_message_fragment, 'course_sock_fragment': course_sock_fragment, 'disable_courseware_js': True, - 'uses_pattern_library': True, + 'uses_bootstrap': True, 'upgrade_price': upgrade_price, 'upgrade_url': upgrade_url, 'has_discount': has_discount, From 76a2523c6eb174607b1d797f55c822de84b53f9c Mon Sep 17 00:00:00 2001 From: Daphne Li-Chen Date: Wed, 24 Jun 2020 19:23:37 -0400 Subject: [PATCH 07/13] AA-203: created API framework for Progress tab, sending user information --- .../course_home_api/progress/v1/__init__.py | 0 .../progress/v1/serializers.py | 14 +++ .../course_home_api/progress/v1/views.py | 102 ++++++++++++++++++ lms/djangoapps/course_home_api/urls.py | 10 ++ 4 files changed, 126 insertions(+) create mode 100644 lms/djangoapps/course_home_api/progress/v1/__init__.py create mode 100644 lms/djangoapps/course_home_api/progress/v1/serializers.py create mode 100644 lms/djangoapps/course_home_api/progress/v1/views.py diff --git a/lms/djangoapps/course_home_api/progress/v1/__init__.py b/lms/djangoapps/course_home_api/progress/v1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/course_home_api/progress/v1/serializers.py b/lms/djangoapps/course_home_api/progress/v1/serializers.py new file mode 100644 index 0000000000..2565bd5ccf --- /dev/null +++ b/lms/djangoapps/course_home_api/progress/v1/serializers.py @@ -0,0 +1,14 @@ +""" +Progress Tab Serializers +""" + +from rest_framework import serializers +from lms.djangoapps.course_home_api.outline.v1.serializers import CourseBlockSerializer + + +class ProgressTabSerializer(serializers.Serializer): + """ + Serializer for progress tab + """ + course_blocks = CourseBlockSerializer() + enrollment_mode = serializers.CharField() diff --git a/lms/djangoapps/course_home_api/progress/v1/views.py b/lms/djangoapps/course_home_api/progress/v1/views.py new file mode 100644 index 0000000000..5cae6e5e4e --- /dev/null +++ b/lms/djangoapps/course_home_api/progress/v1/views.py @@ -0,0 +1,102 @@ +""" +Progress Tab Views +""" + +from rest_framework.generics import RetrieveAPIView +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response + +from edx_django_utils import monitoring as monitoring_utils +from opaque_keys.edx.keys import CourseKey + +from lms.djangoapps.course_home_api.progress.v1.serializers import ProgressTabSerializer + +from student.models import CourseEnrollment, UserTestGroup +from lms.djangoapps.course_api.blocks.transformers.blocks_api import BlocksAPITransformer +from lms.djangoapps.courseware.courses import get_course_with_access +from lms.djangoapps.courseware.masquerade import setup_masquerade +from lms.djangoapps.courseware.access import has_access +from xmodule.modulestore.django import modulestore + +from lms.djangoapps.course_blocks.api import get_course_blocks +import lms.djangoapps.course_blocks.api as course_blocks_api +from openedx.core.djangoapps.content.block_structure.transformers import BlockStructureTransformers + + +class ProgressTabView(RetrieveAPIView): + """ + **Use Cases** + + Request details for the Progress Tab + + **Example Requests** + + GET api/course_home/v1/progress/{course_key} + + **Response Values** + + Body consists of the following fields: + + user: Serialized User object. The serialization has the following fields: + username: (str) The username of the user + email: (str) the email of the user + is_staff: (bool) boolean indicating whether the user has staff permisions or not + course_blocks: + blocks: List of serialized Course Block objects. Each serialization has the following fields: + id: (str) The usage ID of the block. + type: (str) The type of block. Possible values the names of any + XBlock type in the system, including custom blocks. Examples are + course, chapter, sequential, vertical, html, problem, video, and + discussion. + display_name: (str) The display name of the block. + lms_web_url: (str) The URL to the navigational container of the + xBlock on the web LMS. + children: (list) If the block has child blocks, a list of IDs of + the child blocks. + enrollment_mode: (str) a str representing the enrollment the user has ('audit', 'verified', ...) + + **Returns** + + * 200 on success with above fields. + * 403 if the user is not authenticated. + * 404 if the course is not available or cannot be seen. + """ + + permission_classes = (IsAuthenticated,) + serializer_class = ProgressTabSerializer + + def get(self, request, *args, **kwargs): + course_key_string = kwargs.get('course_key_string') + course_key = CourseKey.from_string(course_key_string) + course_usage_key = modulestore().make_course_usage_key(course_key) + + # Enable NR tracing for this view based on course + monitoring_utils.set_custom_metric('course_id', course_key_string) + monitoring_utils.set_custom_metric('user_id', request.user.id) + monitoring_utils.set_custom_metric('is_staff', request.user.is_staff) + + _, request.user = setup_masquerade( + request, + course_key, + staff_access=has_access(request.user, 'staff', course_key), + reset_masquerade_data=True + ) + + transformers = BlockStructureTransformers() + transformers += course_blocks_api.get_course_block_access_transformers(request.user) + transformers += [ + BlocksAPITransformer(None, None, depth=3), + ] + get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) + course_blocks = get_course_blocks(request.user, course_usage_key, transformers, include_completion=True) + + enrollment_mode, _ = CourseEnrollment.enrollment_mode_for_user(request.user, course_key) + + data = { + 'course_blocks': course_blocks, + 'enrollment_mode': enrollment_mode, + } + + serializer = self.get_serializer(data) + + return Response(serializer.data) diff --git a/lms/djangoapps/course_home_api/urls.py b/lms/djangoapps/course_home_api/urls.py index f5e3f94e63..3a2c3c0771 100644 --- a/lms/djangoapps/course_home_api/urls.py +++ b/lms/djangoapps/course_home_api/urls.py @@ -9,6 +9,7 @@ from django.urls import re_path from lms.djangoapps.course_home_api.dates.v1.views import DatesTabView from lms.djangoapps.course_home_api.course_metadata.v1.views import CourseHomeMetadataView from lms.djangoapps.course_home_api.outline.v1.views import OutlineTabView +from lms.djangoapps.course_home_api.progress.v1.views import ProgressTabView urlpatterns = [] @@ -38,3 +39,12 @@ urlpatterns += [ name='course-home-outline-tab' ), ] + +# Progress Tab URLs +urlpatterns += [ + re_path( + r'v1/progress/{}'.format(settings.COURSE_KEY_PATTERN), + ProgressTabView.as_view(), + name='course-home-progress-tab' + ), +] From 9b9259c16071b3b86de3ba5b5fdec568af82c768 Mon Sep 17 00:00:00 2001 From: Robert Raposa Date: Tue, 7 Jul 2020 12:01:08 -0400 Subject: [PATCH 08/13] phase 1: update waffle no request default (#24392) This is Phase 1 of a 2 part rollout. Here, we want to ensure that checking if the waffle flag is set to active for everyone would provide the same value as the current implementation for calls that have no request. The current implementation is to use flag_undefined_default, which is deprecated and we are trying to remove. We are adding a custom metric to see if they match in Production. If all goes well, in Phase 2 we will switch to this new approach. ARCHBOM-1331 --- openedx/core/djangoapps/waffle_utils/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openedx/core/djangoapps/waffle_utils/__init__.py b/openedx/core/djangoapps/waffle_utils/__init__.py index 2b97a3de92..33dc2ef149 100644 --- a/openedx/core/djangoapps/waffle_utils/__init__.py +++ b/openedx/core/djangoapps/waffle_utils/__init__.py @@ -271,11 +271,12 @@ class WaffleFlagNamespace(six.with_metaclass(ABCMeta, WaffleNamespace)): # The callback needs to handle its own caching if it wants it. value = self._cached_flags.get(namespaced_flag_name) if value is None: - + is_flag_active_for_everyone = False if flag_undefined_default is not None: # determine if the flag is undefined in waffle try: - Flag.objects.get(name=namespaced_flag_name) + waffle_flag = Flag.objects.get(name=namespaced_flag_name) + is_flag_active_for_everyone = (waffle_flag.everyone is True) except Flag.DoesNotExist: if flag_undefined_default: # This metric will go away once this has been fully retired with ARCHBOM-132. @@ -289,7 +290,6 @@ class WaffleFlagNamespace(six.with_metaclass(ABCMeta, WaffleNamespace)): value = flag_is_active(request, namespaced_flag_name) else: log.warning(u"%sFlag '%s' accessed without a request", self.log_prefix, namespaced_flag_name) - set_custom_metric('warn_flag_no_request', True) # Return the default value if not in a request context. # Note: this skips the cache as the value might be different # in a normal request context. This case seems to occur when @@ -297,6 +297,9 @@ class WaffleFlagNamespace(six.with_metaclass(ABCMeta, WaffleNamespace)): # the default value. value = bool(flag_undefined_default) self._set_waffle_flag_metric(namespaced_flag_name, value) + no_request_default_match = flag_undefined_default == value + set_custom_metric('temp_flag_no_request_default_match', no_request_default_match) + set_custom_metric('warn_flag_no_request_return_value', value) return value self._cached_flags[namespaced_flag_name] = value From 6cc47a680347a91a0de74261329bb92a4c365883 Mon Sep 17 00:00:00 2001 From: Alex Wang Date: Tue, 7 Jul 2020 13:03:43 -0400 Subject: [PATCH 09/13] update version of edx-proctoring library for MST-297 (#24402) --- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/testing.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index c5deecfc47..22e9ce4f77 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -105,7 +105,7 @@ edx-milestones==0.3.0 # via -r requirements/edx/base.in edx-opaque-keys[django]==2.1.0 # via -r requirements/edx/paver.txt, edx-bulk-grades, edx-ccx-keys, edx-completion, edx-drf-extensions, edx-enterprise, edx-milestones, edx-organizations, edx-proctoring, edx-user-state-client, edx-when, xmodule edx-organizations==5.2.0 # via -r requirements/edx/base.in edx-proctoring-proctortrack==1.0.5 # via -r requirements/edx/base.in -edx-proctoring==2.4.3 # via -r requirements/edx/base.in, edx-proctoring-proctortrack +edx-proctoring==2.4.4 # via -r requirements/edx/base.in, edx-proctoring-proctortrack edx-rbac==1.3.1 # via edx-enterprise edx-rest-api-client==5.2.1 # via -r requirements/edx/base.in, edx-enterprise, edx-proctoring edx-search==1.4.1 # via -r requirements/edx/base.in diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 6a253c75b4..62cd7df1d2 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -118,7 +118,7 @@ edx-milestones==0.3.0 # via -r requirements/edx/testing.txt edx-opaque-keys[django]==2.1.0 # via -r requirements/edx/testing.txt, edx-bulk-grades, edx-ccx-keys, edx-completion, edx-drf-extensions, edx-enterprise, edx-milestones, edx-organizations, edx-proctoring, edx-user-state-client, edx-when, xmodule edx-organizations==5.2.0 # via -r requirements/edx/testing.txt edx-proctoring-proctortrack==1.0.5 # via -r requirements/edx/testing.txt -edx-proctoring==2.4.3 # via -r requirements/edx/testing.txt, edx-proctoring-proctortrack +edx-proctoring==2.4.4 # via -r requirements/edx/testing.txt, edx-proctoring-proctortrack edx-rbac==1.3.1 # via -r requirements/edx/testing.txt, edx-enterprise edx-rest-api-client==5.2.1 # via -r requirements/edx/testing.txt, edx-enterprise, edx-proctoring edx-search==1.4.1 # via -r requirements/edx/testing.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 9dc3a8cc81..e750249b24 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -115,7 +115,7 @@ edx-milestones==0.3.0 # via -r requirements/edx/base.txt edx-opaque-keys[django]==2.1.0 # via -r requirements/edx/base.txt, edx-bulk-grades, edx-ccx-keys, edx-completion, edx-drf-extensions, edx-enterprise, edx-milestones, edx-organizations, edx-proctoring, edx-user-state-client, edx-when, xmodule edx-organizations==5.2.0 # via -r requirements/edx/base.txt edx-proctoring-proctortrack==1.0.5 # via -r requirements/edx/base.txt -edx-proctoring==2.4.3 # via -r requirements/edx/base.txt, edx-proctoring-proctortrack +edx-proctoring==2.4.4 # via -r requirements/edx/base.txt, edx-proctoring-proctortrack edx-rbac==1.3.1 # via -r requirements/edx/base.txt, edx-enterprise edx-rest-api-client==5.2.1 # via -r requirements/edx/base.txt, edx-enterprise, edx-proctoring edx-search==1.4.1 # via -r requirements/edx/base.txt From b81888323e8384096825e92d66522641813045f6 Mon Sep 17 00:00:00 2001 From: Christie Rice <8483753+crice100@users.noreply.github.com> Date: Tue, 7 Jul 2020 14:12:52 -0400 Subject: [PATCH 10/13] Revert "MICROBA-393 Add customized partner report headings" (#24406) --- .../user_api/accounts/serializers.py | 2 - .../accounts/tests/test_retirement_views.py | 100 +++--------------- .../djangoapps/user_api/accounts/views.py | 92 +++------------- 3 files changed, 27 insertions(+), 167 deletions(-) diff --git a/openedx/core/djangoapps/user_api/accounts/serializers.py b/openedx/core/djangoapps/user_api/accounts/serializers.py index 1110bfad9c..7f197cb8b6 100644 --- a/openedx/core/djangoapps/user_api/accounts/serializers.py +++ b/openedx/core/djangoapps/user_api/accounts/serializers.py @@ -475,12 +475,10 @@ class UserRetirementPartnerReportSerializer(serializers.Serializer): Perform serialization for the UserRetirementPartnerReportingStatus model """ user_id = serializers.IntegerField() - student_id = serializers.CharField(required=False) original_username = serializers.CharField() original_email = serializers.EmailField() original_name = serializers.CharField() orgs = serializers.ListField(child=serializers.CharField()) - orgs_config = serializers.ListField(required=False) created = serializers.DateTimeField() # Required overrides of abstract base class methods, but we don't use them diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py index ab44772a3b..b9e17df87c 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_retirement_views.py @@ -46,7 +46,6 @@ from openedx.core.djangoapps.credit.models import ( CreditRequirement, CreditRequirementStatus ) -from openedx.core.djangoapps.external_user_ids.models import ExternalId, ExternalIdType from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory from openedx.core.djangoapps.user_api.accounts.views import AccountRetirementPartnerReportView @@ -288,7 +287,7 @@ class TestPartnerReportingCleanup(ModuleStoreTestCase): def create_partner_reporting_statuses(self, is_being_processed=True, num=2): """ - Creates and returns the given number of test users and UserRetirementPartnerReportingStatuses + Creates and returnes the given number of test users and UserRetirementPartnerReportingStatuses with the given is_being_processed value. """ statuses = [] @@ -483,17 +482,6 @@ class TestPartnerReportingList(ModuleStoreTestCase): """ Tests the partner reporting list endpoint """ - EXPECTED_MB_ORGS_CONFIG = [ - { - AccountRetirementPartnerReportView.ORGS_CONFIG_ORG_KEY: 'mb_coaching', - AccountRetirementPartnerReportView.ORGS_CONFIG_FIELD_HEADINGS_KEY: [ - AccountRetirementPartnerReportView.STUDENT_ID_KEY, - AccountRetirementPartnerReportView.ORIGINAL_EMAIL_KEY, - AccountRetirementPartnerReportView.ORIGINAL_NAME_KEY, - AccountRetirementPartnerReportView.DELETION_COMPLETED_KEY - ] - } - ] def setUp(self): super(TestPartnerReportingList, self).setUp() @@ -505,7 +493,6 @@ class TestPartnerReportingList(ModuleStoreTestCase): self.url = reverse('accounts_retirement_partner_report') self.maxDiff = None self.test_created_datetime = datetime.datetime(2018, 1, 1, tzinfo=pytz.UTC) - ExternalIdType.objects.get_or_create(name=ExternalIdType.MICROBACHELORS_COACHING) def get_user_dict(self, user, enrollments): """ @@ -530,10 +517,9 @@ class TestPartnerReportingList(ModuleStoreTestCase): their processing state to "is_being_processed". Returns a list of user dicts representing what we would expect back from the - endpoint for the given user / enrollment, and a list of the users themselves. + endpoint for the given user / enrollment. """ user_dicts = [] - users = [] courses = self.courses if courses is None else courses for _ in range(num): @@ -554,9 +540,8 @@ class TestPartnerReportingList(ModuleStoreTestCase): user_dicts.append( self.get_user_dict(user, enrollments) ) - users.append(user) - return user_dicts, users + return user_dicts def assert_status_and_user_list(self, expected_users, expected_status=status.HTTP_200_OK): """ @@ -576,19 +561,9 @@ class TestPartnerReportingList(ModuleStoreTestCase): # These sub-lists will fail assertCountEqual if they're out of order for expected_user in expected_users: expected_user['orgs'].sort() - if AccountRetirementPartnerReportView.ORGS_CONFIG_KEY in expected_user: - orgs_config = expected_user[AccountRetirementPartnerReportView.ORGS_CONFIG_KEY] - orgs_config.sort() - for config in orgs_config: - config[AccountRetirementPartnerReportView.ORGS_CONFIG_FIELD_HEADINGS_KEY].sort() for returned_user in returned_users: returned_user['orgs'].sort() - if AccountRetirementPartnerReportView.ORGS_CONFIG_KEY in returned_user: - orgs_config = returned_user[AccountRetirementPartnerReportView.ORGS_CONFIG_KEY] - orgs_config.sort() - for config in orgs_config: - config[AccountRetirementPartnerReportView.ORGS_CONFIG_FIELD_HEADINGS_KEY].sort() self.assertCountEqual(returned_users, expected_users) @@ -596,72 +571,21 @@ class TestPartnerReportingList(ModuleStoreTestCase): """ Basic test to make sure that users in two different orgs are returned. """ - user_dicts, users = self.create_partner_reporting_statuses() - additional_dicts, additional_users = self.create_partner_reporting_statuses(courses=(self.course_awesome_org,)) - user_dicts += additional_dicts + users = self.create_partner_reporting_statuses() + users += self.create_partner_reporting_statuses(courses=(self.course_awesome_org,)) - self.assert_status_and_user_list(user_dicts) + self.assert_status_and_user_list(users) def test_success_multiple_statuses(self): """ Checks that only users in the correct is_being_processed state (False) are returned. """ - user_dicts, users = self.create_partner_reporting_statuses() + users = self.create_partner_reporting_statuses() # These should not come back self.create_partner_reporting_statuses(courses=(self.course_awesome_org,), is_being_processed=True) - self.assert_status_and_user_list(user_dicts) - - def test_success_mb_coaching(self): - """ - Check that MicroBachelors users who have consented to coaching have the proper info - included for the partner report. - """ - path = 'openedx.core.djangoapps.user_api.accounts.views.has_ever_consented_to_coaching' - with mock.patch(path, return_value=True) as mock_has_ever_consented: - user_dicts, users = self.create_partner_reporting_statuses(num=1) - external_id, created = ExternalId.add_new_user_id( - user=users[0], - type_name=ExternalIdType.MICROBACHELORS_COACHING - ) - - expected_user = user_dicts[0] - expected_users = [expected_user] - expected_user[AccountRetirementPartnerReportView.STUDENT_ID_KEY] = str(external_id.external_user_id) - expected_user[ - AccountRetirementPartnerReportView.ORGS_CONFIG_KEY] = TestPartnerReportingList.EXPECTED_MB_ORGS_CONFIG - - self.assert_status_and_user_list(expected_users) - mock_has_ever_consented.assert_called_once() - - def test_success_mb_coaching_no_external_id(self): - """ - Check that MicroBachelors users who have consented to coaching, but who do not have an external id, have the - proper info included for the partner report. - """ - path = 'openedx.core.djangoapps.user_api.accounts.views.has_ever_consented_to_coaching' - with mock.patch(path, return_value=True) as mock_has_ever_consented: - user_dicts, users = self.create_partner_reporting_statuses(num=1) - - self.assert_status_and_user_list(user_dicts) - mock_has_ever_consented.assert_called_once() - - def test_success_mb_coaching_no_consent(self): - """ - Check that MicroBachelors users who have not consented to coaching have the proper info - included for the partner report. - """ - path = 'openedx.core.djangoapps.user_api.accounts.views.has_ever_consented_to_coaching' - with mock.patch(path, return_value=False) as mock_has_ever_consented: - user_dicts, users = self.create_partner_reporting_statuses(num=1) - ExternalId.add_new_user_id( - user=users[0], - type_name=ExternalIdType.MICROBACHELORS_COACHING - ) - - self.assert_status_and_user_list(user_dicts) - mock_has_ever_consented.assert_called_once() + self.assert_status_and_user_list(users) def test_no_users(self): """ @@ -682,10 +606,10 @@ class TestPartnerReportingList(ModuleStoreTestCase): Checks that users are progressed to "is_being_processed" True upon being returned from this call. """ - user_dicts, users = self.create_partner_reporting_statuses() + users = self.create_partner_reporting_statuses() # First time through we should get the users - self.assert_status_and_user_list(user_dicts) + self.assert_status_and_user_list(users) # Second time they should be updated to is_being_processed=True self.assert_status_and_user_list([]) @@ -1181,7 +1105,7 @@ class TestAccountRetirementUpdate(RetirementTestCase): data = {'new_state': 'LOCKING_ACCOUNT', 'response': 'this should succeed'} self.update_and_assert_status(data) - # Refresh the retirement object and confirm the messages and state are correct + # Refresh the retirment object and confirm the messages and state are correct retirement = UserRetirementStatus.objects.get(id=self.retirement.id) self.assertEqual(retirement.current_state, RetirementState.objects.get(state_name='LOCKING_ACCOUNT')) self.assertEqual(retirement.last_state, RetirementState.objects.get(state_name='PENDING')) @@ -1203,7 +1127,7 @@ class TestAccountRetirementUpdate(RetirementTestCase): for update_data in fake_retire_process: self.update_and_assert_status(update_data) - # Refresh the retirement object and confirm the messages and state are correct + # Refresh the retirment object and confirm the messages and state are correct retirement = UserRetirementStatus.objects.get(id=self.retirement.id) self.assertEqual(retirement.current_state, RetirementState.objects.get(state_name='COMPLETE')) self.assertEqual(retirement.last_state, RetirementState.objects.get(state_name='CREDENTIALS_COMPLETE')) diff --git a/openedx/core/djangoapps/user_api/accounts/views.py b/openedx/core/djangoapps/user_api/accounts/views.py index 0719ebe217..c319af66d6 100644 --- a/openedx/core/djangoapps/user_api/accounts/views.py +++ b/openedx/core/djangoapps/user_api/accounts/views.py @@ -44,7 +44,6 @@ from openedx.core.djangoapps.ace_common.template_context import get_base_templat from openedx.core.djangoapps.api_admin.models import ApiAccessRequest from openedx.core.djangoapps.course_groups.models import UnregisteredLearnerCohortAssignments from openedx.core.djangoapps.credit.models import CreditRequest, CreditRequirementStatus -from openedx.core.djangoapps.external_user_ids.models import ExternalId, ExternalIdType from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY from openedx.core.djangoapps.profile_images.images import remove_profile_images from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_names, set_has_profile_image @@ -83,11 +82,6 @@ from .permissions import CanDeactivateUser, CanReplaceUsername, CanRetireUser from .serializers import UserRetirementPartnerReportSerializer, UserRetirementStatusSerializer from .signals import USER_RETIRE_LMS_CRITICAL, USER_RETIRE_LMS_MISC, USER_RETIRE_MAILINGS -try: - from coaching.api import has_ever_consented_to_coaching -except ImportError: - has_ever_consented_to_coaching = None - log = logging.getLogger(__name__) USER_PROFILE_PII = { @@ -536,14 +530,6 @@ class AccountRetirementPartnerReportView(ViewSet): Provides API endpoints for managing partner reporting of retired users. """ - DELETION_COMPLETED_KEY = 'deletion_completed' - ORGS_CONFIG_KEY = 'orgs_config' - ORGS_CONFIG_ORG_KEY = 'org' - ORGS_CONFIG_FIELD_HEADINGS_KEY = 'field_headings' - ORIGINAL_EMAIL_KEY = 'original_email' - ORIGINAL_NAME_KEY = 'original_name' - STUDENT_ID_KEY = 'student_id' - authentication_classes = (JwtAuthentication,) permission_classes = (permissions.IsAuthenticated, CanRetireUser,) parser_classes = (JSONParser,) @@ -558,7 +544,7 @@ class AccountRetirementPartnerReportView(ViewSet): for enrollment in user.courseenrollment_set.all(): org = enrollment.course_id.org - # Org can conceivably be blank or this bogus default value + # Org can concievably be blank or this bogus default value if org and org != 'outdated_entry': orgs.add(org) try: @@ -583,9 +569,17 @@ class AccountRetirementPartnerReportView(ViewSet): is_being_processed=False ).order_by('id') - retirements = [] - for retirement_status in retirement_statuses: - retirements.append(self._get_retirement_for_partner_report(retirement_status)) + retirements = [ + { + 'user_id': retirement.user.pk, + 'original_username': retirement.original_username, + 'original_email': retirement.original_email, + 'original_name': retirement.original_name, + 'orgs': self._get_orgs_for_user(retirement.user), + 'created': retirement.created, + } + for retirement in retirement_statuses + ] serializer = UserRetirementPartnerReportSerializer(retirements, many=True) @@ -593,62 +587,6 @@ class AccountRetirementPartnerReportView(ViewSet): return Response(serializer.data) - def _get_retirement_for_partner_report(self, retirement_status): - """ - Get the retirement for this retirement_status. The retirement info will be included in the partner report. - """ - retirement = { - 'user_id': retirement_status.user.pk, - 'original_username': retirement_status.original_username, - AccountRetirementPartnerReportView.ORIGINAL_EMAIL_KEY: retirement_status.original_email, - AccountRetirementPartnerReportView.ORIGINAL_NAME_KEY: retirement_status.original_name, - 'orgs': self._get_orgs_for_user(retirement_status.user), - 'created': retirement_status.created, - } - - # Some orgs have a custom list of headings and content for the partner report. Add this, if applicable. - self._add_orgs_config_for_user(retirement, retirement_status.user) - - return retirement - - def _add_orgs_config_for_user(self, retirement, user): - """ - Check to see if the user's info was sent to any partners (orgs) that have a a custom list of headings and - content for the partner report. If so, add this. - """ - # See if the MicroBachelors coaching provider needs to be notified of this user's retirement - if has_ever_consented_to_coaching is not None and has_ever_consented_to_coaching(user): - # See if the user has a MicroBachelors external id. If not, they were never sent to the - # coaching provider. - external_ids = ExternalId.objects.filter( - user=user, - external_id_type__name=ExternalIdType.MICROBACHELORS_COACHING - ) - if external_ids.exists(): - # User has an external id. Add the additional info. - external_id = str(external_ids[0].external_user_id) - self._add_coaching_orgs_config(retirement, external_id) - - def _add_coaching_orgs_config(self, retirement, external_id): - """ - Add the orgs configuration for MicroBachelors coaching - """ - # Add the custom field headings - retirement[AccountRetirementPartnerReportView.ORGS_CONFIG_KEY] = [ - { - AccountRetirementPartnerReportView.ORGS_CONFIG_ORG_KEY: 'mb_coaching', - AccountRetirementPartnerReportView.ORGS_CONFIG_FIELD_HEADINGS_KEY: [ - AccountRetirementPartnerReportView.STUDENT_ID_KEY, - AccountRetirementPartnerReportView.ORIGINAL_EMAIL_KEY, - AccountRetirementPartnerReportView.ORIGINAL_NAME_KEY, - AccountRetirementPartnerReportView.DELETION_COMPLETED_KEY - ] - } - ] - - # Add the custom field value - retirement[AccountRetirementPartnerReportView.STUDENT_ID_KEY] = external_id - @request_requires_username def retirement_partner_status_create(self, request): """ @@ -769,7 +707,7 @@ class AccountRetirementStatusView(ViewSet): ) serializer = UserRetirementStatusSerializer(retirements, many=True) return Response(serializer.data) - # This should only occur on the int() conversion of cool_off_days at this point + # This should only occur on the int() converstion of cool_off_days at this point except ValueError: return Response('Invalid cool_off_days, should be integer.', status=status.HTTP_400_BAD_REQUEST) except KeyError as exc: @@ -1118,7 +1056,7 @@ class UsernameReplacementView(APIView): updates usernames across all services. DO NOT run this alone or users will not match across the system and things will be broken. - API will receive a list of current usernames and their requested new + API will recieve a list of current usernames and their requested new username. If their new username is taken, it will randomly assign a new username. This API will be called first, before calling the APIs in other services as this @@ -1228,7 +1166,7 @@ class UsernameReplacementView(APIView): """ Generates a unique username. If the desired username is available, that will be returned. - Otherwise it will generate unique suffixes to the desired username until it is an available username. + Otherwise it will generate unique suffixs to the desired username until it is an available username. """ new_username = desired_username # Keep checking usernames in case desired_username + random suffix is already taken From 10ab63995a998d9a4300339616e7f8c4e9cdc9b4 Mon Sep 17 00:00:00 2001 From: Robert Raposa Date: Tue, 7 Jul 2020 15:07:59 -0400 Subject: [PATCH 11/13] remove temporary USE_DEFAULT_TRUE_NAMESPACE (#24404) Once USE_DEFAULT_TRUE_NAMESPACE is fully rolled out and proves to return True where we wish, we can remove this temporary roll-out flag introduced in: https://github.com/edx/edx-platform/pull/24322 ARCHBOM-1316 --- .../features/course_experience/__init__.py | 29 ++----------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/openedx/features/course_experience/__init__.py b/openedx/features/course_experience/__init__.py index 5bbe6004e8..4ebf33e98f 100644 --- a/openedx/features/course_experience/__init__.py +++ b/openedx/features/course_experience/__init__.py @@ -2,7 +2,6 @@ Unified course experience settings and helper methods. """ import crum -from django.conf import settings from django.utils.translation import ugettext as _ from edx_django_utils.monitoring import set_custom_metric from waffle import flag_is_active @@ -14,19 +13,6 @@ from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlag, W # Namespace for course experience waffle flags. WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='course_experience') -# .. toggle_name: USE_DEFAULT_TRUE_NAMESPACE -# .. toggle_implementation: DjangoSetting -# .. toggle_default: False -# .. toggle_description: When True, uses the new default_true namespace to help deprecate flag_undefined_default. -# .. toggle_category: course_experience -# .. toggle_use_cases: monitored_rollout -# .. toggle_creation_date: 2020-06-30 -# .. toggle_expiration_date: 2020-07 -# .. toggle_warnings: n/a -# .. toggle_tickets: n/a -# .. toggle_status: supported -_USE_DEFAULT_TRUE_NAMESPACE = 'USE_DEFAULT_TRUE_NAMESPACE' - class DefaultTrueWaffleFlagNamespace(WaffleFlagNamespace): """ @@ -36,9 +22,9 @@ class DefaultTrueWaffleFlagNamespace(WaffleFlagNamespace): and refactor/fix any tests that shouldn't be removed. """ - def _is_flag_active(self, flag_name): + def is_flag_active(self, flag_name, check_before_waffle_callback=None, flag_undefined_default=None): """ - Returns and caches whether the provided flag is active. + Overrides is_flag_active, and returns and caches whether the provided flag is active. If the flag value is already cached in the request, it is returned. If the flag doesn't exist, always returns default of True. @@ -80,17 +66,6 @@ class DefaultTrueWaffleFlagNamespace(WaffleFlagNamespace): self._set_waffle_flag_metric(namespaced_flag_name, value) return value - def is_flag_active(self, flag_name, check_before_waffle_callback=None, flag_undefined_default=None): - """ - Overrides is_flag_active if setting USE_DEFAULT_TRUE_NAMESPACE is True. - """ - use_default_true_namespace = getattr(settings, _USE_DEFAULT_TRUE_NAMESPACE, False) - set_custom_metric('temp_use_default_true_namespace', use_default_true_namespace) - if use_default_true_namespace: - return self._is_flag_active(flag_name) - else: - return super().is_flag_active(flag_name, check_before_waffle_callback, flag_undefined_default=True) - DEFAULT_TRUE_WAFFLE_FLAG_NAMESPACE = DefaultTrueWaffleFlagNamespace(name='course_experience') From 4ac48c5ff800bc090d3f576a03ff359e9b3810fe Mon Sep 17 00:00:00 2001 From: Kellie Selinka Date: Tue, 7 Jul 2020 15:47:31 -0400 Subject: [PATCH 12/13] update edx-enterprise version ENT-3073 --- requirements/constraints.txt | 2 +- requirements/edx/base.txt | 2 +- requirements/edx/development.txt | 2 +- requirements/edx/testing.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index b1d2aef1c4..48f1107e80 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -43,7 +43,7 @@ drf-jwt==1.14.0 # The team that owns this package will manually bump this package rather than having it pulled in automatically. # This is to allow them to better control its deployment and to do it in a process that works better # for them. -edx-enterprise==3.3.15 +edx-enterprise==3.3.17 # Upgrading to 2.12.0 breaks several test classes due to API changes, need to update our code accordingly factory-boy==2.8.1 diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 22e9ce4f77..29325fae43 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -99,7 +99,7 @@ edx-django-release-util==0.4.4 # via -r requirements/edx/base.in edx-django-sites-extensions==2.5.1 # via -r requirements/edx/base.in edx-django-utils==3.2.3 # via -r requirements/edx/base.in, django-config-models, edx-drf-extensions, edx-enterprise, edx-rest-api-client, edx-when edx-drf-extensions==6.1.0 # via -r requirements/edx/base.in, edx-completion, edx-enterprise, edx-organizations, edx-proctoring, edx-rbac, edx-when, edxval -edx-enterprise==3.3.15 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in +edx-enterprise==3.3.17 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in edx-i18n-tools==0.5.3 # via ora2 edx-milestones==0.3.0 # via -r requirements/edx/base.in edx-opaque-keys[django]==2.1.0 # via -r requirements/edx/paver.txt, edx-bulk-grades, edx-ccx-keys, edx-completion, edx-drf-extensions, edx-enterprise, edx-milestones, edx-organizations, edx-proctoring, edx-user-state-client, edx-when, xmodule diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 62cd7df1d2..e17ce22ec6 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -111,7 +111,7 @@ edx-django-release-util==0.4.4 # via -r requirements/edx/testing.txt edx-django-sites-extensions==2.5.1 # via -r requirements/edx/testing.txt edx-django-utils==3.2.3 # via -r requirements/edx/testing.txt, django-config-models, edx-drf-extensions, edx-enterprise, edx-rest-api-client, edx-when edx-drf-extensions==6.1.0 # via -r requirements/edx/testing.txt, edx-completion, edx-enterprise, edx-organizations, edx-proctoring, edx-rbac, edx-when, edxval -edx-enterprise==3.3.15 # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt +edx-enterprise==3.3.17 # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt edx-i18n-tools==0.5.3 # via -r requirements/edx/testing.txt, ora2 edx-lint==1.5.0 # via -r requirements/edx/testing.txt edx-milestones==0.3.0 # via -r requirements/edx/testing.txt diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index e750249b24..fa92bf1420 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -108,7 +108,7 @@ edx-django-release-util==0.4.4 # via -r requirements/edx/base.txt edx-django-sites-extensions==2.5.1 # via -r requirements/edx/base.txt edx-django-utils==3.2.3 # via -r requirements/edx/base.txt, django-config-models, edx-drf-extensions, edx-enterprise, edx-rest-api-client, edx-when edx-drf-extensions==6.1.0 # via -r requirements/edx/base.txt, edx-completion, edx-enterprise, edx-organizations, edx-proctoring, edx-rbac, edx-when, edxval -edx-enterprise==3.3.15 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt +edx-enterprise==3.3.17 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt edx-i18n-tools==0.5.3 # via -r requirements/edx/base.txt, -r requirements/edx/testing.in, ora2 edx-lint==1.5.0 # via -r requirements/edx/testing.in edx-milestones==0.3.0 # via -r requirements/edx/base.txt From 081a751d2c6d757ec1f70f3f910c9e5b8f054e8f Mon Sep 17 00:00:00 2001 From: Robert Raposa Date: Tue, 7 Jul 2020 16:32:11 -0400 Subject: [PATCH 13/13] fix temp_flag_no_request_default_match (#24409) Fix bug in metric temp_flag_no_request_default_match. Metric renamed to temp_flag_no_request_default_match_2 to ensure we are looking at the right data. This extends Phase 1 of the rollout, as documented in: https://github.com/edx/edx-platform/pull/24392 ARCHBOM-1331 --- openedx/core/djangoapps/waffle_utils/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openedx/core/djangoapps/waffle_utils/__init__.py b/openedx/core/djangoapps/waffle_utils/__init__.py index 33dc2ef149..0cec6c7ddd 100644 --- a/openedx/core/djangoapps/waffle_utils/__init__.py +++ b/openedx/core/djangoapps/waffle_utils/__init__.py @@ -297,8 +297,8 @@ class WaffleFlagNamespace(six.with_metaclass(ABCMeta, WaffleNamespace)): # the default value. value = bool(flag_undefined_default) self._set_waffle_flag_metric(namespaced_flag_name, value) - no_request_default_match = flag_undefined_default == value - set_custom_metric('temp_flag_no_request_default_match', no_request_default_match) + no_request_default_match = is_flag_active_for_everyone == value + set_custom_metric('temp_flag_no_request_default_match_2', no_request_default_match) set_custom_metric('warn_flag_no_request_return_value', value) return value