From a791759202eef7374b11d9d6a1c51a01b63a0f7d Mon Sep 17 00:00:00 2001 From: Shafqat Farhan Date: Sun, 21 Aug 2022 22:09:40 +0500 Subject: [PATCH] feat: VAN-1051 - Integrated Optimizely fullstack Client and experiment --- cms/envs/common.py | 4 +++ common/djangoapps/student/models.py | 23 +++++++++++++++++ common/djangoapps/student/tests/tests.py | 1 + lms/djangoapps/utils.py | 22 ++++++++++++++++ lms/envs/common.py | 1 + requirements/edx/base.in | 1 + requirements/edx/base.txt | 28 ++++++++++++++++++-- requirements/edx/development.txt | 33 +++++++++++++++++++----- requirements/edx/testing.txt | 32 ++++++++++++++++++++++- 9 files changed, 136 insertions(+), 9 deletions(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index 7fbf7bc64d..c54416237c 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -635,6 +635,10 @@ DJFS = { ######################## BRANCH.IO ########################### BRANCH_IO_KEY = '' +######################## OPTIMIZELY ########################### +OPTIMIZELY_PROJECT_ID = None +OPTIMIZELY_FULLSTACK_SDK_KEY = None + ######################## GOOGLE ANALYTICS ########################### GOOGLE_ANALYTICS_ACCOUNT = None diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 50521cafaf..2219afb572 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -59,6 +59,7 @@ from simple_history.models import HistoricalRecords from user_util import user_util import openedx.core.djangoapps.django_comment_common.comment_client as cc +from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY from common.djangoapps.course_modes.models import CourseMode, get_cosmetic_verified_display_price from common.djangoapps.student.email_helpers import ( generate_proctoring_requirements_email_context, @@ -76,6 +77,7 @@ from lms.djangoapps.courseware.models import ( OrgDynamicUpgradeDeadlineConfiguration, ) from lms.djangoapps.courseware.toggles import streak_celebration_is_active +from lms.djangoapps.utils import OptimizelyClient from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.enrollments.api import ( @@ -1557,7 +1559,10 @@ class CourseEnrollment(models.Model): Emits an event to explicitly track course enrollment and unenrollment. """ from openedx.core.djangoapps.schedules.config import set_up_external_updates_for_enrollment + from openedx.core.djangoapps.user_api.preferences.api import get_user_preference + from openedx.features.enterprise_support.utils import is_enterprise_learner + optimizely_client = OptimizelyClient.get_optimizely_client() try: context = contexts.course_context_from_course_id(self.course_id) if enterprise_uuid: @@ -1603,6 +1608,24 @@ class CourseEnrollment(models.Model): if is_personalized_recommendation is not None: segment_properties['is_personalized_recommendation'] = is_personalized_recommendation + # TODO: VAN-1052 - This is Optimizely's A/B experimentation block to test welcome email redesign. + # Remove this temporary block after pausing the experiment. + optimizely_experiment_variation = None + if optimizely_client: + optimizely_experiment_variation = optimizely_client.activate( + 'welcome_email_redesign_experiment', + str(self.user.id), + { + 'lang_preference': get_user_preference(self.user, LANGUAGE_KEY), + 'is_enterprise_user': is_enterprise_learner(self.user), + } + ) + optimizely_client.track('welcome_email_sent', str(self.user.id)) + + # Set this property to True only if the welcome email redesign Optimizely experiment is running + # and user_id falls in required variation. + segment_properties['redesign_email'] = optimizely_experiment_variation == 'redesign_email_enabled' + with tracker.get_tracker().context(event_name, context): tracker.emit(event_name, data) segment.track(self.user_id, event_name, segment_properties, traits=segment_traits) diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index a2e4c8a5de..14a3c360e8 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -706,6 +706,7 @@ class EnrollmentEventTestMixin(EventTestMixin): 'external_course_updates': -1, 'course_start': course.start, 'course_pacing': course.pacing, + 'redesign_email': False, }) return properties, traits diff --git a/lms/djangoapps/utils.py b/lms/djangoapps/utils.py index 182c382ca2..cef65f1ee6 100644 --- a/lms/djangoapps/utils.py +++ b/lms/djangoapps/utils.py @@ -4,6 +4,8 @@ Helper Methods from braze.client import BrazeClient from django.conf import settings +from optimizely import optimizely +from optimizely.config_manager import PollingConfigManager def _get_key(key_or_id, key_cls): @@ -31,3 +33,23 @@ def get_braze_client(): api_url=braze_api_url, app_id='', ) + + +class OptimizelyClient: + """ Class for instantiating an Optimizely full stack client instance. """ + optimizely_client = None + + @classmethod + def get_optimizely_client(cls): + if not cls.optimizely_client: + optimizely_sdk_key = settings.OPTIMIZELY_FULLSTACK_SDK_KEY + if not optimizely_sdk_key: + return None + + config_manager = PollingConfigManager( + update_interval=10, + sdk_key=optimizely_sdk_key, + ) + cls.optimizely_client = optimizely.Optimizely(config_manager=config_manager) + + return cls.optimizely_client diff --git a/lms/envs/common.py b/lms/envs/common.py index ceaa8e9f33..f8347a0403 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1509,6 +1509,7 @@ BRANCH_IO_KEY = '' ######################## OPTIMIZELY ########################### OPTIMIZELY_PROJECT_ID = None +OPTIMIZELY_FULLSTACK_SDK_KEY = None ######################## subdomain specific settings ########################### COURSE_LISTINGS = {} diff --git a/requirements/edx/base.in b/requirements/edx/base.in index cd5697dcda..d1a7db1fd4 100644 --- a/requirements/edx/base.in +++ b/requirements/edx/base.in @@ -125,6 +125,7 @@ oauthlib # OAuth specification support for authentica openedx-calc # Library supporting mathematical calculations for Open edX openedx-events # Open edX Events from Hooks Extension Framework (OEP-50) openedx-filters # Open edX Filters from Hooks Extension Framework (OEP-50) +optimizely-sdk # Optimizely full stack SDK for Python ora2>=4.4.0 outcome-surveys # edx-platform plugin to send and track segment events needed for surveys path diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 15480460d3..17c6eaf9d7 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -48,6 +48,7 @@ attrs==22.1.0 # aiohttp # blockstore # edx-ace + # jsonschema # openedx-events babel==2.10.3 # via @@ -157,6 +158,7 @@ cryptography==36.0.2 # django-fernet-fields # edx-enterprise # jwcrypto + # optimizely-sdk # pyjwt # pyopenssl # snowflake-connector-python @@ -288,6 +290,7 @@ django-filter==22.1 # -r requirements/edx/base.in # blockstore # edx-enterprise + # learner-pathway-progress # lti-consumer-xblock django-ipware==4.0.2 # via @@ -398,6 +401,7 @@ djangorestframework==3.12.4 # edx-organizations # edx-proctoring # edx-submissions + # learner-pathway-progress # ora2 # super-csv djangorestframework-xml==2.0.0 @@ -475,6 +479,7 @@ edx-drf-extensions==8.0.1 # edx-rbac # edx-when # edxval + # learner-pathway-progress edx-enterprise==3.56.5 # via # -c requirements/edx/../constraints.txt @@ -598,11 +603,14 @@ icalendar==4.1.0 idna==3.3 # via # -r requirements/edx/paver.txt + # optimizely-sdk # requests # snowflake-connector-python # yarl importlib-metadata==4.12.0 # via markdown +importlib-resources==5.9.0 + # via jsonschema inflection==0.5.1 # via drf-yasg interchange==2021.0.4 @@ -636,6 +644,8 @@ jsonfield==3.1.0 # lti-consumer-xblock # ora2 # outcome-surveys +jsonschema==4.14.0 + # via optimizely-sdk jwcrypto==1.3.1 # via pylti1p3 kombu==5.2.4 @@ -750,6 +760,8 @@ openedx-filters==0.7.0 # via # -r requirements/edx/base.in # lti-consumer-xblock +optimizely-sdk==4.1.0 + # via -r requirements/edx/base.in ora2==4.4.7 # via -r requirements/edx/base.in oscrypto==1.3.0 @@ -787,6 +799,8 @@ pillow==9.2.0 # -r requirements/edx/base.in # edx-enterprise # edx-organizations +pkgutil-resolve-name==1.3.10 + # via jsonschema polib==1.1.1 # via edx-i18n-tools prompt-toolkit==3.0.30 @@ -850,12 +864,18 @@ pynacl==1.5.0 pynliner==0.8.0 # via -r requirements/edx/base.in pyopenssl==22.0.0 - # via snowflake-connector-python + # via + # optimizely-sdk + # snowflake-connector-python pyparsing==3.0.9 # via # chem # openedx-calc # packaging +pyrsistent==0.18.1 + # via + # jsonschema + # optimizely-sdk pysrt==1.1.2 # via # -r requirements/edx/base.in @@ -942,6 +962,7 @@ requests==2.28.1 # geoip2 # learner-pathway-progress # mailsnake + # optimizely-sdk # pyjwkest # pylti1p3 # python-swiftclient @@ -1008,6 +1029,7 @@ six==1.16.0 # interchange # isodate # libsass + # optimizely-sdk # pansi # paver # py2neo @@ -1161,7 +1183,9 @@ xss-utils==0.4.0 yarl==1.8.1 # via aiohttp zipp==3.8.1 - # via importlib-metadata + # via + # importlib-metadata + # importlib-resources # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index b61b9bb007..40000a6441 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -228,6 +228,7 @@ cryptography==36.0.2 # django-fernet-fields # edx-enterprise # jwcrypto + # optimizely-sdk # pyjwt # pyopenssl # snowflake-connector-python @@ -386,6 +387,7 @@ django-filter==22.1 # -r requirements/edx/testing.txt # blockstore # edx-enterprise + # learner-pathway-progress # lti-consumer-xblock django-ipware==4.0.2 # via @@ -502,6 +504,7 @@ djangorestframework==3.12.4 # edx-organizations # edx-proctoring # edx-submissions + # learner-pathway-progress # ora2 # super-csv djangorestframework-xml==2.0.0 @@ -590,6 +593,7 @@ edx-drf-extensions==8.0.1 # edx-rbac # edx-when # edxval + # learner-pathway-progress edx-enterprise==3.56.5 # via # -c requirements/edx/../constraints.txt @@ -757,6 +761,7 @@ idna==3.3 # via # -r requirements/edx/testing.txt # anyio + # optimizely-sdk # requests # snowflake-connector-python # yarl @@ -769,7 +774,9 @@ importlib-metadata==4.12.0 # pytest-randomly # sphinx importlib-resources==5.9.0 - # via jsonschema + # via + # -r requirements/edx/testing.txt + # jsonschema inflection==0.5.1 # via # -r requirements/edx/testing.txt @@ -827,8 +834,11 @@ jsonfield==3.1.0 # lti-consumer-xblock # ora2 # outcome-surveys -jsonschema==4.9.0 - # via sphinxcontrib-openapi +jsonschema==4.14.0 + # via + # -r requirements/edx/testing.txt + # optimizely-sdk + # sphinxcontrib-openapi jwcrypto==1.3.1 # via # -r requirements/edx/testing.txt @@ -978,6 +988,8 @@ openedx-filters==0.7.0 # via # -r requirements/edx/testing.txt # lti-consumer-xblock +optimizely-sdk==4.1.0 + # via -r requirements/edx/testing.txt ora2==4.4.7 # via -r requirements/edx/testing.txt oscrypto==1.3.0 @@ -1034,7 +1046,9 @@ pillow==9.2.0 pip-tools==6.8.0 # via -r requirements/edx/pip-tools.txt pkgutil-resolve-name==1.3.10 - # via jsonschema + # via + # -r requirements/edx/testing.txt + # jsonschema platformdirs==2.5.2 # via # -r requirements/edx/testing.txt @@ -1075,8 +1089,9 @@ pyblake2==1.1.2 # -r requirements/edx/testing.txt # blockstore pycodestyle==2.8.0 + # via # -c requirements/edx/../constraints.txt - # via -r requirements/edx/testing.txt + # -r requirements/edx/testing.txt pycountry==22.3.5 # via -r requirements/edx/testing.txt pycparser==2.21 @@ -1163,6 +1178,7 @@ pynliner==0.8.0 pyopenssl==22.0.0 # via # -r requirements/edx/testing.txt + # optimizely-sdk # snowflake-connector-python pyparsing==3.0.9 # via @@ -1174,7 +1190,10 @@ pyparsing==3.0.9 pyquery==1.4.3 # via -r requirements/edx/testing.txt pyrsistent==0.18.1 - # via jsonschema + # via + # -r requirements/edx/testing.txt + # jsonschema + # optimizely-sdk pysrt==1.1.2 # via # -r requirements/edx/testing.txt @@ -1304,6 +1323,7 @@ requests==2.28.1 # geoip2 # learner-pathway-progress # mailsnake + # optimizely-sdk # pact-python # pyjwkest # pylti1p3 @@ -1391,6 +1411,7 @@ six==1.16.0 # interchange # isodate # libsass + # optimizely-sdk # pact-python # pansi # paver diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 38603fe2ed..ec2f841daf 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -68,6 +68,7 @@ attrs==22.1.0 # aiohttp # blockstore # edx-ace + # jsonschema # openedx-events # outcome # pytest @@ -216,6 +217,7 @@ cryptography==36.0.2 # django-fernet-fields # edx-enterprise # jwcrypto + # optimizely-sdk # pyjwt # pyopenssl # snowflake-connector-python @@ -367,6 +369,7 @@ django-filter==22.1 # -r requirements/edx/base.txt # blockstore # edx-enterprise + # learner-pathway-progress # lti-consumer-xblock django-ipware==4.0.2 # via @@ -483,6 +486,7 @@ djangorestframework==3.12.4 # edx-organizations # edx-proctoring # edx-submissions + # learner-pathway-progress # ora2 # super-csv djangorestframework-xml==2.0.0 @@ -569,6 +573,7 @@ edx-drf-extensions==8.0.1 # edx-rbac # edx-when # edxval + # learner-pathway-progress edx-enterprise==3.56.5 # via # -c requirements/edx/../constraints.txt @@ -726,6 +731,7 @@ idna==3.3 # via # -r requirements/edx/base.txt # anyio + # optimizely-sdk # requests # snowflake-connector-python # yarl @@ -734,6 +740,10 @@ importlib-metadata==4.12.0 # -r requirements/edx/base.txt # markdown # pytest-randomly +importlib-resources==5.9.0 + # via + # -r requirements/edx/base.txt + # jsonschema inflection==0.5.1 # via # -r requirements/edx/base.txt @@ -789,6 +799,10 @@ jsonfield==3.1.0 # lti-consumer-xblock # ora2 # outcome-surveys +jsonschema==4.14.0 + # via + # -r requirements/edx/base.txt + # optimizely-sdk jwcrypto==1.3.1 # via # -r requirements/edx/base.txt @@ -925,6 +939,8 @@ openedx-filters==0.7.0 # via # -r requirements/edx/base.txt # lti-consumer-xblock +optimizely-sdk==4.1.0 + # via -r requirements/edx/base.txt ora2==4.4.7 # via -r requirements/edx/base.txt oscrypto==1.3.0 @@ -971,6 +987,10 @@ pillow==9.2.0 # -r requirements/edx/base.txt # edx-enterprise # edx-organizations +pkgutil-resolve-name==1.3.10 + # via + # -r requirements/edx/base.txt + # jsonschema platformdirs==2.5.2 # via # pylint @@ -1010,8 +1030,9 @@ pyblake2==1.1.2 # -r requirements/edx/base.txt # blockstore pycodestyle==2.8.0 + # via # -c requirements/edx/../constraints.txt - # via -r requirements/edx/testing.in + # -r requirements/edx/testing.in pycountry==22.3.5 # via -r requirements/edx/base.txt pycparser==2.21 @@ -1090,6 +1111,7 @@ pynliner==0.8.0 pyopenssl==22.0.0 # via # -r requirements/edx/base.txt + # optimizely-sdk # snowflake-connector-python pyparsing==3.0.9 # via @@ -1099,6 +1121,11 @@ pyparsing==3.0.9 # packaging pyquery==1.4.3 # via -r requirements/edx/testing.in +pyrsistent==0.18.1 + # via + # -r requirements/edx/base.txt + # jsonschema + # optimizely-sdk pysrt==1.1.2 # via # -r requirements/edx/base.txt @@ -1223,6 +1250,7 @@ requests==2.28.1 # geoip2 # learner-pathway-progress # mailsnake + # optimizely-sdk # pact-python # pyjwkest # pylti1p3 @@ -1308,6 +1336,7 @@ six==1.16.0 # interchange # isodate # libsass + # optimizely-sdk # pact-python # pansi # paver @@ -1526,6 +1555,7 @@ zipp==3.8.1 # via # -r requirements/edx/base.txt # importlib-metadata + # importlib-resources # The following packages are considered to be unsafe in a requirements file: # setuptools