From 7f5eb296a3a8140981cb36f4b1606e29b722f6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Fernanda=20Magallanes=20Z?= Date: Fri, 20 May 2022 13:48:59 -0400 Subject: [PATCH 01/10] feat: add mfe_api v1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds MFE API. This is part of the work that is being done to obtain the MFE Runtime Configurations and that has been discussed in the BTR WG. Discussion: https://discuss.openedx.org/t/how-to-use-microfrontend-in-a-multitenant-instance/6936/14?u=mafermazu MFE Runtime configuration - eduNEXT: https://docs.google.com/document/d/1-FHIQmyeQZu3311x8eYUNMru4JX7Yb3UlqjmJxvM8do/edit?usp=sharing feat: add lms setting to set mfe config cache (#262) Co-authored-by: MarĂ­a Fernanda Magallanes Z feat: make mfe config api disabled by default (#263) * feat: make mfe config api disabled by default * fix: simple is better than complex test: add mfe config tests (#264) * test: add mfe config tests * test: fix it and simplify it * test: correct pylint issues fix: correct pep 8 violations fix: add mfe api unit test in github workflow fix: correct unit tests refactor: move mfe api to lms fix: try mfe api urls without regex fix: add app_namespace in lms urls fix: try url without conditional Revert "fix: try url without conditional" This reverts commit 694aab546134b4bd9ad2642e24927b42cac24459. fix: set enable_mfe_config_api feature to true in the tests test: try to add failed test case Revert "test: try to add failed test case" This reverts commit cee6bf656ab1b96492b0b6199ddff32a6d6a65bd. docs: improve explanation and documentation fix: ensure the response is a json object refactor: be consistent with the variable names fix: allow overriding mfe api config cache timeout in production fix: handle 404 response in view refactor: use a guard instead if-else feat: add the possibility to show mfe specific config --- .github/workflows/pylint-checks.yml | 2 +- .github/workflows/unit-test-shards.json | 1 + lms/djangoapps/mfe_api/__init__.py | 0 lms/djangoapps/mfe_api/tests/__init__.py | 0 lms/djangoapps/mfe_api/tests/test_views.py | 43 ++++++++++++++++++++++ lms/djangoapps/mfe_api/urls.py | 10 +++++ lms/djangoapps/mfe_api/views.py | 42 +++++++++++++++++++++ lms/envs/common.py | 21 +++++++++++ lms/envs/production.py | 3 ++ lms/envs/test.py | 3 ++ lms/urls.py | 5 +++ 11 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 lms/djangoapps/mfe_api/__init__.py create mode 100644 lms/djangoapps/mfe_api/tests/__init__.py create mode 100644 lms/djangoapps/mfe_api/tests/test_views.py create mode 100644 lms/djangoapps/mfe_api/urls.py create mode 100644 lms/djangoapps/mfe_api/views.py diff --git a/.github/workflows/pylint-checks.yml b/.github/workflows/pylint-checks.yml index f1e3d02bbae4..43677f6bc10d 100644 --- a/.github/workflows/pylint-checks.yml +++ b/.github/workflows/pylint-checks.yml @@ -17,7 +17,7 @@ jobs: - module-name: lms-1 path: "lms/djangoapps/badges/ lms/djangoapps/branding/ lms/djangoapps/bulk_email/ lms/djangoapps/bulk_enroll/ lms/djangoapps/bulk_user_retirement/ lms/djangoapps/ccx/ lms/djangoapps/certificates/ lms/djangoapps/commerce/ lms/djangoapps/course_api/ lms/djangoapps/course_blocks/ lms/djangoapps/course_home_api/ lms/djangoapps/course_wiki/ lms/djangoapps/coursewarehistoryextended/ lms/djangoapps/debug/ lms/djangoapps/courseware/ lms/djangoapps/course_goals/ lms/djangoapps/rss_proxy/ lms/djangoapps/save_for_later/" - module-name: lms-2 - path: "lms/djangoapps/gating/ lms/djangoapps/grades/ lms/djangoapps/instructor/ lms/djangoapps/instructor_analytics/ lms/djangoapps/discussion/ lms/djangoapps/edxnotes/ lms/djangoapps/email_marketing/ lms/djangoapps/experiments/ lms/djangoapps/instructor_task/ lms/djangoapps/learner_dashboard/ lms/djangoapps/lms_initialization/ lms/djangoapps/lms_xblock/ lms/djangoapps/lti_provider/ lms/djangoapps/mailing/ lms/djangoapps/mobile_api/ lms/djangoapps/monitoring/ lms/djangoapps/ora_staff_grader/ lms/djangoapps/program_enrollments/ lms/djangoapps/rss_proxy lms/djangoapps/static_template_view/ lms/djangoapps/staticbook/ lms/djangoapps/support/ lms/djangoapps/survey/ lms/djangoapps/teams/ lms/djangoapps/tests/ lms/djangoapps/user_tours/ lms/djangoapps/verify_student/ lms/envs/ lms/lib/ lms/tests.py" + path: "lms/djangoapps/gating/ lms/djangoapps/grades/ lms/djangoapps/instructor/ lms/djangoapps/instructor_analytics/ lms/djangoapps/discussion/ lms/djangoapps/edxnotes/ lms/djangoapps/email_marketing/ lms/djangoapps/experiments/ lms/djangoapps/instructor_task/ lms/djangoapps/learner_dashboard/ lms/djangoapps/lms_initialization/ lms/djangoapps/lms_xblock/ lms/djangoapps/lti_provider/ lms/djangoapps/mailing/ lms/djangoapps/mobile_api/ lms/djangoapps/monitoring/ lms/djangoapps/ora_staff_grader/ lms/djangoapps/program_enrollments/ lms/djangoapps/rss_proxy lms/djangoapps/static_template_view/ lms/djangoapps/staticbook/ lms/djangoapps/support/ lms/djangoapps/survey/ lms/djangoapps/teams/ lms/djangoapps/tests/ lms/djangoapps/user_tours/ lms/djangoapps/verify_student/ lms/djangoapps/mfe_api/ lms/envs/ lms/lib/ lms/tests.py" - module-name: openedx-1 path: "openedx/core/types/ openedx/core/djangoapps/ace_common/ openedx/core/djangoapps/agreements/ openedx/core/djangoapps/api_admin/ openedx/core/djangoapps/auth_exchange/ openedx/core/djangoapps/bookmarks/ openedx/core/djangoapps/cache_toolbox/ openedx/core/djangoapps/catalog/ openedx/core/djangoapps/ccxcon/ openedx/core/djangoapps/commerce/ openedx/core/djangoapps/common_initialization/ openedx/core/djangoapps/common_views/ openedx/core/djangoapps/config_model_utils/ openedx/core/djangoapps/content/ openedx/core/djangoapps/content_libraries/ openedx/core/djangoapps/contentserver/ openedx/core/djangoapps/cookie_metadata/ openedx/core/djangoapps/cors_csrf/ openedx/core/djangoapps/course_apps/ openedx/core/djangoapps/course_date_signals/ openedx/core/djangoapps/course_groups/ openedx/core/djangoapps/courseware_api/ openedx/core/djangoapps/crawlers/ openedx/core/djangoapps/credentials/ openedx/core/djangoapps/credit/ openedx/core/djangoapps/dark_lang/ openedx/core/djangoapps/debug/ openedx/core/djangoapps/demographics/ openedx/core/djangoapps/discussions/ openedx/core/djangoapps/django_comment_common/ openedx/core/djangoapps/embargo/ openedx/core/djangoapps/enrollments/ openedx/core/djangoapps/external_user_ids/ openedx/core/djangoapps/zendesk_proxy/ openedx/core/djangolib/ openedx/core/lib/ openedx/core/tests/ openedx/core/djangoapps/course_live/" - module-name: openedx-2 diff --git a/.github/workflows/unit-test-shards.json b/.github/workflows/unit-test-shards.json index 15d5ce270ea9..c63ce0f97c67 100644 --- a/.github/workflows/unit-test-shards.json +++ b/.github/workflows/unit-test-shards.json @@ -71,6 +71,7 @@ "lms/djangoapps/tests/", "lms/djangoapps/user_tours/", "lms/djangoapps/verify_student/", + "lms/djangoapps/mfe_api/", "lms/envs/", "lms/lib/", "lms/tests.py" diff --git a/lms/djangoapps/mfe_api/__init__.py b/lms/djangoapps/mfe_api/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lms/djangoapps/mfe_api/tests/__init__.py b/lms/djangoapps/mfe_api/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/lms/djangoapps/mfe_api/tests/test_views.py b/lms/djangoapps/mfe_api/tests/test_views.py new file mode 100644 index 000000000000..2b28bd87fcfb --- /dev/null +++ b/lms/djangoapps/mfe_api/tests/test_views.py @@ -0,0 +1,43 @@ +""" +Test the use cases of the views of the mfe api. +""" + +from unittest.mock import patch + +from django.conf import settings +from django.urls import reverse +from rest_framework import status + +from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers +from openedx.core.lib.api.test_utils import ApiTestCase + + +class MFEConfigTestCase(ApiTestCase): + """ + Test the use case that exposes the site configuration with the mfe api. + """ + def setUp(self): + self.mfe_config_api_url = reverse('mfe_api:config') + return super().setUp() + + def test_get_mfe_config(self): + """Test the get mfe config from site configuration with the mfe api. + + Expected result: + - Inside self.get_json pass the response is a json and the status is 200 asserts. + - The configuration obtained by the api is equal to its site configuration in the + MFE_CONFIG key. + """ + mfe_config = configuration_helpers.get_value('MFE_CONFIG', {}) + response_json = self.get_json(self.mfe_config_api_url) + assert response_json == mfe_config + + @patch.dict(settings.FEATURES, {'ENABLE_MFE_API': False}) + def test_404_get_mfe_config(self): + """Test the 404 not found response from get mfe config. + + Expected result: + - Response status code equal to 404 + """ + response = self.client.get(self.mfe_config_api_url) + assert response.status_code == status.HTTP_404_NOT_FOUND diff --git a/lms/djangoapps/mfe_api/urls.py b/lms/djangoapps/mfe_api/urls.py new file mode 100644 index 000000000000..5cc5152345bb --- /dev/null +++ b/lms/djangoapps/mfe_api/urls.py @@ -0,0 +1,10 @@ +""" URLs configuration for the mfe api.""" + +from django.urls import path + +from lms.djangoapps.mfe_api.views import MFEConfigView + +app_name = 'mfe_api' +urlpatterns = [ + path('v1/config', MFEConfigView.as_view(), name='config'), +] diff --git a/lms/djangoapps/mfe_api/views.py b/lms/djangoapps/mfe_api/views.py new file mode 100644 index 000000000000..40764b4e6cad --- /dev/null +++ b/lms/djangoapps/mfe_api/views.py @@ -0,0 +1,42 @@ +""" +MFE API Views for useful information related to mfes. +""" + +from django.conf import settings +from django.http import JsonResponse +from django.utils.decorators import method_decorator +from django.views.decorators.cache import cache_page +from rest_framework import status +from rest_framework.views import APIView + +from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers + + +class MFEConfigView(APIView): + """ + Provides an API endpoint to get the MFE_CONFIG from site configuration. + """ + + @method_decorator(cache_page(settings.MFE_API_CONFIG_CACHE_TIMEOUT)) + def get(self, request): + """ + GET /api/mfe/v1/config + + **GET Response Values** + ``` + { + "LOGO_URL": "https://example.com/logo.png", + } + ``` + """ + + if not settings.FEATURES.get('ENABLE_MFE_API'): + msg = 'MFE API not found. Try setting FEATURES["ENABLE_MFE_API"] to true.' + return JsonResponse({'message': msg}, status=status.HTTP_404_NOT_FOUND) + + mfe_config = {'MFE_CONFIG': configuration_helpers.get_value('MFE_CONFIG', {})} + if request.query_params.get('mfe'): + mfe = str(request.query_params.get('mfe')).upper() + mfe_config[f'MFE_CONFIG_{mfe}']= configuration_helpers.get_value(f'MFE_CONFIG_{mfe}',{}) + + return JsonResponse(mfe_config, status=status.HTTP_200_OK) diff --git a/lms/envs/common.py b/lms/envs/common.py index d61433ed9e28..0934236dc170 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1018,6 +1018,18 @@ # .. toggle_target_removal_date: None # .. toggle_tickets: 'https://openedx.atlassian.net/browse/MST-1458' 'ENABLE_CERTIFICATES_IDV_REQUIREMENT': False, + + # .. toggle_name: FEATURES['ENABLE_MFE_API'] + # .. toggle_implementation: DjangoSetting + # .. toggle_default: False + # .. toggle_description: Set to True to enable MFE Config REST API. This is disabled by + # default. + # .. toggle_use_cases: open_edx + # .. toggle_creation_date: 2022-05-20 + # .. toggle_target_removal_date: None + # .. toggle_warnings: None + # .. toggle_tickets: None + 'ENABLE_MFE_API': False, } # Specifies extra XBlock fields that should available when requested via the Course Blocks API @@ -3251,6 +3263,9 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring # Blockstore 'blockstore.apps.bundles', + + # MFE API + 'lms.djangoapps.mfe_api', ] ######################### CSRF ######################################### @@ -4909,6 +4924,12 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring # .. toggle_tickets: https://openedx.atlassian.net/browse/VAN-838 ENABLE_DYNAMIC_REGISTRATION_FIELDS = False +# .. setting_name: MFE_API_CONFIG_CACHE_TIMEOUT +# .. setting_default: 60*5 +# .. setting_description: The MFE_CONFIG site configuration will be cached during the +# specified time +MFE_API_CONFIG_CACHE_TIMEOUT = 60 * 5 + ############### Settings for the ace_common plugin ################# # Note that all settings are actually defined by the plugin # pylint: disable=wrong-import-position diff --git a/lms/envs/production.py b/lms/envs/production.py index fabcc024efbb..b1b0b6bc6efb 100644 --- a/lms/envs/production.py +++ b/lms/envs/production.py @@ -1078,3 +1078,6 @@ def get_env_setting(setting): "SECRET": ENV_TOKENS.get('BIG_BLUE_BUTTON_GLOBAL_SECRET', None), "URL": ENV_TOKENS.get('BIG_BLUE_BUTTON_GLOBAL_URL', None), } + +############################# MFE API CONFIG CACHE TIMEOUT############################ +MFE_API_CONFIG_CACHE_TIMEOUT = ENV_TOKENS.get('MFE_API_CONFIG_CACHE_TIMEOUT', MFE_API_CONFIG_CACHE_TIMEOUT) diff --git a/lms/envs/test.py b/lms/envs/test.py index edef277574eb..4186d98e4575 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -647,3 +647,6 @@ "SECRET": "***", "URL": "***", } + +################## MFE API #################### +FEATURES['ENABLE_MFE_API'] = True diff --git a/lms/urls.py b/lms/urls.py index 352a25dee599..974e14bc2787 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -1022,3 +1022,8 @@ urlpatterns += [ path('api/instructor_task/', include('lms.djangoapps.instructor_task.rest_api.urls')), ] + +# MFE API urls +urlpatterns += [ + path('api/mfe/', include(('lms.djangoapps.mfe_api.urls', 'lms.djangoapps.mfe_api'), namespace='mfe_api')) +] From 9069ce995027222601593c9031f6d3368ef72548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Fernanda=20Magallanes=20Z?= Date: Thu, 9 Jun 2022 08:53:48 -0400 Subject: [PATCH 02/10] fix: get merged response from mfe_config and mfe_config_mymfe --- lms/djangoapps/mfe_api/tests/test_views.py | 15 +++++++++++++++ lms/djangoapps/mfe_api/views.py | 16 +++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/mfe_api/tests/test_views.py b/lms/djangoapps/mfe_api/tests/test_views.py index 2b28bd87fcfb..a8994ab098a4 100644 --- a/lms/djangoapps/mfe_api/tests/test_views.py +++ b/lms/djangoapps/mfe_api/tests/test_views.py @@ -32,6 +32,21 @@ def test_get_mfe_config(self): response_json = self.get_json(self.mfe_config_api_url) assert response_json == mfe_config + def test_get_mfe_config_with_queryparams(self): + """Test the get mfe config with a query params from site configuration. + + Expected result: + - Inside self.get_json pass the response is a json and the status is 200 asserts. + - The configuration obtained by the api is equal to its site configuration in the + MFE_CONFIG and MFE_CONFIG_MYMFE merged on top. + """ + mfe_config = configuration_helpers.get_value('MFE_CONFIG', {}) + mfe = "mymfe" + mfe_config.update(configuration_helpers.get_value(f'MFE_CONFIG_{mfe.upper()}', {})) + + response_json = self.get_json(f'{self.mfe_config_api_url}?mfe={mfe}') + assert response_json == mfe_config + @patch.dict(settings.FEATURES, {'ENABLE_MFE_API': False}) def test_404_get_mfe_config(self): """Test the 404 not found response from get mfe config. diff --git a/lms/djangoapps/mfe_api/views.py b/lms/djangoapps/mfe_api/views.py index 40764b4e6cad..10e4c86ea51b 100644 --- a/lms/djangoapps/mfe_api/views.py +++ b/lms/djangoapps/mfe_api/views.py @@ -21,11 +21,21 @@ class MFEConfigView(APIView): def get(self, request): """ GET /api/mfe/v1/config + or + GET /api/mfe/v1/config?mfe=name_of_mfe **GET Response Values** ``` { - "LOGO_URL": "https://example.com/logo.png", + "BASE_URL": "https://name_of_mfe.example.com", + "LANGUAGE_PREFERENCE_COOKIE_NAME": "example-language-preference", + "CREDENTIALS_BASE_URL": "https://credentials.example.com", + "DOSCOVERY_API_BASE_URL": "https://discovery.example.com", + "LMS_BASE_URL": "https://courses.example.com", + "LOGIN_URL": "https://courses.example.com/login", + "LOGOUT_URL": "https://courses.example.com/logout", + "STUDIO_BASE_URL": "https://studio.example.com", + "LOGO_URL": "https://courses.example.com/logo.png" } ``` """ @@ -34,9 +44,9 @@ def get(self, request): msg = 'MFE API not found. Try setting FEATURES["ENABLE_MFE_API"] to true.' return JsonResponse({'message': msg}, status=status.HTTP_404_NOT_FOUND) - mfe_config = {'MFE_CONFIG': configuration_helpers.get_value('MFE_CONFIG', {})} + mfe_config = configuration_helpers.get_value('MFE_CONFIG', {}) if request.query_params.get('mfe'): mfe = str(request.query_params.get('mfe')).upper() - mfe_config[f'MFE_CONFIG_{mfe}']= configuration_helpers.get_value(f'MFE_CONFIG_{mfe}',{}) + mfe_config.update(configuration_helpers.get_value(f'MFE_CONFIG_{mfe}', {})) return JsonResponse(mfe_config, status=status.HTTP_200_OK) From 05b602f7ddbb476e4bdcdb278722f39bd4306d82 Mon Sep 17 00:00:00 2001 From: Maria Fernanda Magallanes Zubillaga Date: Thu, 23 Jun 2022 21:24:33 -0400 Subject: [PATCH 03/10] refactor: remove unnecessary config --- lms/envs/production.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lms/envs/production.py b/lms/envs/production.py index b1b0b6bc6efb..fabcc024efbb 100644 --- a/lms/envs/production.py +++ b/lms/envs/production.py @@ -1078,6 +1078,3 @@ def get_env_setting(setting): "SECRET": ENV_TOKENS.get('BIG_BLUE_BUTTON_GLOBAL_SECRET', None), "URL": ENV_TOKENS.get('BIG_BLUE_BUTTON_GLOBAL_URL', None), } - -############################# MFE API CONFIG CACHE TIMEOUT############################ -MFE_API_CONFIG_CACHE_TIMEOUT = ENV_TOKENS.get('MFE_API_CONFIG_CACHE_TIMEOUT', MFE_API_CONFIG_CACHE_TIMEOUT) From 60e6de05fce70c817791f1c650a2df639a5145be Mon Sep 17 00:00:00 2001 From: Maria Fernanda Magallanes Zubillaga Date: Thu, 23 Jun 2022 22:25:15 -0400 Subject: [PATCH 04/10] refactor: set ENABLE_MFE_API as a top level setting --- lms/djangoapps/mfe_api/tests/test_views.py | 6 ++-- lms/djangoapps/mfe_api/views.py | 9 +++--- lms/envs/common.py | 36 +++++++++++----------- lms/envs/test.py | 2 +- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/lms/djangoapps/mfe_api/tests/test_views.py b/lms/djangoapps/mfe_api/tests/test_views.py index a8994ab098a4..b17ca460dc16 100644 --- a/lms/djangoapps/mfe_api/tests/test_views.py +++ b/lms/djangoapps/mfe_api/tests/test_views.py @@ -2,9 +2,7 @@ Test the use cases of the views of the mfe api. """ -from unittest.mock import patch - -from django.conf import settings +from django.test import override_settings from django.urls import reverse from rest_framework import status @@ -47,7 +45,7 @@ def test_get_mfe_config_with_queryparams(self): response_json = self.get_json(f'{self.mfe_config_api_url}?mfe={mfe}') assert response_json == mfe_config - @patch.dict(settings.FEATURES, {'ENABLE_MFE_API': False}) + @override_settings(ENABLE_MFE_API=False) def test_404_get_mfe_config(self): """Test the 404 not found response from get mfe config. diff --git a/lms/djangoapps/mfe_api/views.py b/lms/djangoapps/mfe_api/views.py index 10e4c86ea51b..db874c8479c5 100644 --- a/lms/djangoapps/mfe_api/views.py +++ b/lms/djangoapps/mfe_api/views.py @@ -3,7 +3,7 @@ """ from django.conf import settings -from django.http import JsonResponse +from django.http import HttpResponseNotFound, JsonResponse from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page from rest_framework import status @@ -30,7 +30,7 @@ def get(self, request): "BASE_URL": "https://name_of_mfe.example.com", "LANGUAGE_PREFERENCE_COOKIE_NAME": "example-language-preference", "CREDENTIALS_BASE_URL": "https://credentials.example.com", - "DOSCOVERY_API_BASE_URL": "https://discovery.example.com", + "DISCOVERY_API_BASE_URL": "https://discovery.example.com", "LMS_BASE_URL": "https://courses.example.com", "LOGIN_URL": "https://courses.example.com/login", "LOGOUT_URL": "https://courses.example.com/logout", @@ -40,9 +40,8 @@ def get(self, request): ``` """ - if not settings.FEATURES.get('ENABLE_MFE_API'): - msg = 'MFE API not found. Try setting FEATURES["ENABLE_MFE_API"] to true.' - return JsonResponse({'message': msg}, status=status.HTTP_404_NOT_FOUND) + if not settings.ENABLE_MFE_API: + return HttpResponseNotFound() mfe_config = configuration_helpers.get_value('MFE_CONFIG', {}) if request.query_params.get('mfe'): diff --git a/lms/envs/common.py b/lms/envs/common.py index 0934236dc170..8befc45934a7 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1018,18 +1018,6 @@ # .. toggle_target_removal_date: None # .. toggle_tickets: 'https://openedx.atlassian.net/browse/MST-1458' 'ENABLE_CERTIFICATES_IDV_REQUIREMENT': False, - - # .. toggle_name: FEATURES['ENABLE_MFE_API'] - # .. toggle_implementation: DjangoSetting - # .. toggle_default: False - # .. toggle_description: Set to True to enable MFE Config REST API. This is disabled by - # default. - # .. toggle_use_cases: open_edx - # .. toggle_creation_date: 2022-05-20 - # .. toggle_target_removal_date: None - # .. toggle_warnings: None - # .. toggle_tickets: None - 'ENABLE_MFE_API': False, } # Specifies extra XBlock fields that should available when requested via the Course Blocks API @@ -4924,12 +4912,6 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring # .. toggle_tickets: https://openedx.atlassian.net/browse/VAN-838 ENABLE_DYNAMIC_REGISTRATION_FIELDS = False -# .. setting_name: MFE_API_CONFIG_CACHE_TIMEOUT -# .. setting_default: 60*5 -# .. setting_description: The MFE_CONFIG site configuration will be cached during the -# specified time -MFE_API_CONFIG_CACHE_TIMEOUT = 60 * 5 - ############### Settings for the ace_common plugin ################# # Note that all settings are actually defined by the plugin # pylint: disable=wrong-import-position @@ -5160,3 +5142,21 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring COURSE_LIVE_GLOBAL_CREDENTIALS = {} PERSONALIZED_RECOMMENDATION_COOKIE_NAME = 'edx-user-personalized-recommendation' + +# .. toggle_name: ENABLE_MFE_API +# .. toggle_implementation: DjangoSetting +# .. toggle_default: False +# .. toggle_description: Set to True to enable MFE Config REST API. This is disabled by +# default. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2022-05-20 +# .. toggle_target_removal_date: None +# .. toggle_warnings: None +# .. toggle_tickets: None +ENABLE_MFE_API = False + +# .. setting_name: MFE_API_CONFIG_CACHE_TIMEOUT +# .. setting_default: 60*5 +# .. setting_description: The MFE_CONFIG site configuration will be cached during the +# specified time +MFE_API_CONFIG_CACHE_TIMEOUT = 60 * 5 diff --git a/lms/envs/test.py b/lms/envs/test.py index 4186d98e4575..8d2f65ca3b6b 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -649,4 +649,4 @@ } ################## MFE API #################### -FEATURES['ENABLE_MFE_API'] = True +ENABLE_MFE_API = True From 7f47cd1b8a7c9a3575e3ad64164878294467e488 Mon Sep 17 00:00:00 2001 From: Maria Fernanda Magallanes Zubillaga Date: Fri, 24 Jun 2022 01:48:43 -0400 Subject: [PATCH 05/10] test: add more unit tests --- lms/djangoapps/mfe_api/tests/test_views.py | 74 +++++++++++++++++----- 1 file changed, 57 insertions(+), 17 deletions(-) diff --git a/lms/djangoapps/mfe_api/tests/test_views.py b/lms/djangoapps/mfe_api/tests/test_views.py index b17ca460dc16..603ee84a8f04 100644 --- a/lms/djangoapps/mfe_api/tests/test_views.py +++ b/lms/djangoapps/mfe_api/tests/test_views.py @@ -2,23 +2,25 @@ Test the use cases of the views of the mfe api. """ +from unittest.mock import call, patch + +import ddt from django.test import override_settings from django.urls import reverse from rest_framework import status +from rest_framework.test import APITestCase -from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers -from openedx.core.lib.api.test_utils import ApiTestCase - - -class MFEConfigTestCase(ApiTestCase): +@ddt.ddt +class MFEConfigTestCase(APITestCase): """ Test the use case that exposes the site configuration with the mfe api. """ def setUp(self): - self.mfe_config_api_url = reverse('mfe_api:config') + self.mfe_config_api_url = reverse("mfe_api:config") return super().setUp() - def test_get_mfe_config(self): + @patch("lms.djangoapps.mfe_api.views.configuration_helpers") + def test_get_mfe_config(self, configuration_helpers_mock): """Test the get mfe config from site configuration with the mfe api. Expected result: @@ -26,11 +28,15 @@ def test_get_mfe_config(self): - The configuration obtained by the api is equal to its site configuration in the MFE_CONFIG key. """ - mfe_config = configuration_helpers.get_value('MFE_CONFIG', {}) - response_json = self.get_json(self.mfe_config_api_url) - assert response_json == mfe_config + configuration_helpers_mock.get_value.return_value = {"logo":"logo.jpg"} + response = self.client.get(self.mfe_config_api_url) + + configuration_helpers_mock.get_value.assert_called_once_with("MFE_CONFIG", {}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json(), {"logo":"logo.jpg"}) - def test_get_mfe_config_with_queryparams(self): + @patch("lms.djangoapps.mfe_api.views.configuration_helpers") + def test_get_mfe_config_with_queryparams(self, configuration_helpers_mock): """Test the get mfe config with a query params from site configuration. Expected result: @@ -38,19 +44,53 @@ def test_get_mfe_config_with_queryparams(self): - The configuration obtained by the api is equal to its site configuration in the MFE_CONFIG and MFE_CONFIG_MYMFE merged on top. """ - mfe_config = configuration_helpers.get_value('MFE_CONFIG', {}) - mfe = "mymfe" - mfe_config.update(configuration_helpers.get_value(f'MFE_CONFIG_{mfe.upper()}', {})) + configuration_helpers_mock.get_value.side_effect = [{"logo":"logo.jpg", "other": "other"}, + {"logo":"logo_mymfe.jpg"}] + + response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") + self.assertEqual(response.status_code, status.HTTP_200_OK) + calls = [call("MFE_CONFIG", {}), call("MFE_CONFIG_MYMFE", {})] + configuration_helpers_mock.get_value.assert_has_calls(calls) + self.assertEqual(response.json(), {"logo":"logo_mymfe.jpg", "other":"other"}) + + @patch("lms.djangoapps.mfe_api.views.configuration_helpers") + @ddt.data( + [{}, {}, {}], + [{"logo":"logo.jpg"}, {}, {"logo":"logo.jpg"}], + [{}, {"logo":"logo_mymfe.jpg"}, {"logo":"logo_mymfe.jpg"}], + [{"logo":"logo.jpg"}, {"logo":"logo_mymfe.jpg"}, {"logo":"logo_mymfe.jpg"}], + [{"logo":"logo.jpg", "other": "other"}, {"logo":"logo_mymfe.jpg"}, {"logo":"logo_mymfe.jpg", "other":"other"}], + ) + @ddt.unpack + def test_get_mfe_config_with_queryparams_other_cases( + self, + mfe_config, + mfe_config_mymfe, + expected_response, + configuration_helpers_mock + ): + """_summary_ + + Args: + configuration_helpers_mock (_type_): _description_ + """ + + configuration_helpers_mock.get_value.side_effect = [mfe_config, mfe_config_mymfe] - response_json = self.get_json(f'{self.mfe_config_api_url}?mfe={mfe}') - assert response_json == mfe_config + response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") + self.assertEqual(response.status_code, status.HTTP_200_OK) + calls = [call("MFE_CONFIG", {}), call("MFE_CONFIG_MYMFE", {})] + configuration_helpers_mock.get_value.assert_has_calls(calls) + self.assertEqual(response.json(), expected_response) + @patch("lms.djangoapps.mfe_api.views.configuration_helpers") @override_settings(ENABLE_MFE_API=False) - def test_404_get_mfe_config(self): + def test_404_get_mfe_config(self, configuration_helpers_mock): """Test the 404 not found response from get mfe config. Expected result: - Response status code equal to 404 """ response = self.client.get(self.mfe_config_api_url) + configuration_helpers_mock.get_value.assert_not_called() assert response.status_code == status.HTTP_404_NOT_FOUND From 39dec5740fce1b350c6c2347eb8f1d66fda270e5 Mon Sep 17 00:00:00 2001 From: Maria Fernanda Magallanes Zubillaga Date: Thu, 30 Jun 2022 20:47:23 -0400 Subject: [PATCH 06/10] fix: correct pep8 violations --- lms/djangoapps/mfe_api/tests/test_views.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lms/djangoapps/mfe_api/tests/test_views.py b/lms/djangoapps/mfe_api/tests/test_views.py index 603ee84a8f04..b3592d87a0e7 100644 --- a/lms/djangoapps/mfe_api/tests/test_views.py +++ b/lms/djangoapps/mfe_api/tests/test_views.py @@ -10,6 +10,7 @@ from rest_framework import status from rest_framework.test import APITestCase + @ddt.ddt class MFEConfigTestCase(APITestCase): """ @@ -28,12 +29,12 @@ def test_get_mfe_config(self, configuration_helpers_mock): - The configuration obtained by the api is equal to its site configuration in the MFE_CONFIG key. """ - configuration_helpers_mock.get_value.return_value = {"logo":"logo.jpg"} + configuration_helpers_mock.get_value.return_value = {"logo": "logo.jpg"} response = self.client.get(self.mfe_config_api_url) configuration_helpers_mock.get_value.assert_called_once_with("MFE_CONFIG", {}) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json(), {"logo":"logo.jpg"}) + self.assertEqual(response.json(), {"logo": "logo.jpg"}) @patch("lms.djangoapps.mfe_api.views.configuration_helpers") def test_get_mfe_config_with_queryparams(self, configuration_helpers_mock): @@ -44,22 +45,23 @@ def test_get_mfe_config_with_queryparams(self, configuration_helpers_mock): - The configuration obtained by the api is equal to its site configuration in the MFE_CONFIG and MFE_CONFIG_MYMFE merged on top. """ - configuration_helpers_mock.get_value.side_effect = [{"logo":"logo.jpg", "other": "other"}, - {"logo":"logo_mymfe.jpg"}] + configuration_helpers_mock.get_value.side_effect = [{"logo": "logo.jpg", "other": "other"}, + {"logo": "logo_mymfe.jpg"}] response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") self.assertEqual(response.status_code, status.HTTP_200_OK) calls = [call("MFE_CONFIG", {}), call("MFE_CONFIG_MYMFE", {})] configuration_helpers_mock.get_value.assert_has_calls(calls) - self.assertEqual(response.json(), {"logo":"logo_mymfe.jpg", "other":"other"}) + self.assertEqual(response.json(), {"logo": "logo_mymfe.jpg", "other": "other"}) @patch("lms.djangoapps.mfe_api.views.configuration_helpers") @ddt.data( [{}, {}, {}], - [{"logo":"logo.jpg"}, {}, {"logo":"logo.jpg"}], - [{}, {"logo":"logo_mymfe.jpg"}, {"logo":"logo_mymfe.jpg"}], - [{"logo":"logo.jpg"}, {"logo":"logo_mymfe.jpg"}, {"logo":"logo_mymfe.jpg"}], - [{"logo":"logo.jpg", "other": "other"}, {"logo":"logo_mymfe.jpg"}, {"logo":"logo_mymfe.jpg", "other":"other"}], + [{"logo": "logo.jpg"}, {}, {"logo": "logo.jpg"}], + [{}, {"logo": "logo_mymfe.jpg"}, {"logo": "logo_mymfe.jpg"}], + [{"logo": "logo.jpg"}, {"logo": "logo_mymfe.jpg"}, {"logo": "logo_mymfe.jpg"}], + [{"logo": "logo.jpg", "other": "other"}, {"logo": "logo_mymfe.jpg"}, + {"logo": "logo_mymfe.jpg", "other": "other"}], ) @ddt.unpack def test_get_mfe_config_with_queryparams_other_cases( @@ -68,7 +70,7 @@ def test_get_mfe_config_with_queryparams_other_cases( mfe_config_mymfe, expected_response, configuration_helpers_mock - ): + ): """_summary_ Args: From 547ceaad8a19fa044233c4d4b5288f4b2eb13a82 Mon Sep 17 00:00:00 2001 From: Maria Fernanda Magallanes Zubillaga Date: Thu, 30 Jun 2022 22:08:49 -0400 Subject: [PATCH 07/10] docs: update test docs --- lms/djangoapps/mfe_api/tests/test_views.py | 59 +++++++++++++--------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/lms/djangoapps/mfe_api/tests/test_views.py b/lms/djangoapps/mfe_api/tests/test_views.py index b3592d87a0e7..8084f563a1d3 100644 --- a/lms/djangoapps/mfe_api/tests/test_views.py +++ b/lms/djangoapps/mfe_api/tests/test_views.py @@ -25,58 +25,66 @@ def test_get_mfe_config(self, configuration_helpers_mock): """Test the get mfe config from site configuration with the mfe api. Expected result: - - Inside self.get_json pass the response is a json and the status is 200 asserts. - - The configuration obtained by the api is equal to its site configuration in the - MFE_CONFIG key. + - The get_value method of the configuration_helpers in the views is called once with the + parameters ("MFE_CONFIG", {}). + - The status of the response of the request is a HTTP_200_OK. + - The json of the response of the request is equal to the mocked configuration. """ - configuration_helpers_mock.get_value.return_value = {"logo": "logo.jpg"} + configuration_helpers_mock.get_value.return_value = {"EXAMPLE_VAR": "value"} response = self.client.get(self.mfe_config_api_url) configuration_helpers_mock.get_value.assert_called_once_with("MFE_CONFIG", {}) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json(), {"logo": "logo.jpg"}) + self.assertEqual(response.json(), {"EXAMPLE_VAR": "value"}) @patch("lms.djangoapps.mfe_api.views.configuration_helpers") - def test_get_mfe_config_with_queryparams(self, configuration_helpers_mock): - """Test the get mfe config with a query params from site configuration. + def test_get_mfe_config_with_queryparam(self, configuration_helpers_mock): + """Test the get mfe config with a query param from site configuration. Expected result: - - Inside self.get_json pass the response is a json and the status is 200 asserts. - - The configuration obtained by the api is equal to its site configuration in the - MFE_CONFIG and MFE_CONFIG_MYMFE merged on top. + - The get_value method of the configuration_helpers in the views is called twice, once with the + parameters ("MFE_CONFIG", {}) and once with the parameters ("MFE_CONFIG_MYMFE", {}). + and one for get_value("MFE_CONFIG_MYMFE", {}). + - The json of the response is the merge of both mocked configurations. """ - configuration_helpers_mock.get_value.side_effect = [{"logo": "logo.jpg", "other": "other"}, - {"logo": "logo_mymfe.jpg"}] + configuration_helpers_mock.get_value.side_effect = [{"EXAMPLE_VAR": "value", "OTHER": "other"}, + {"EXAMPLE_VAR": "mymfe_value"}] response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") self.assertEqual(response.status_code, status.HTTP_200_OK) calls = [call("MFE_CONFIG", {}), call("MFE_CONFIG_MYMFE", {})] configuration_helpers_mock.get_value.assert_has_calls(calls) - self.assertEqual(response.json(), {"logo": "logo_mymfe.jpg", "other": "other"}) + self.assertEqual(response.json(), {"EXAMPLE_VAR": "mymfe_value", "OTHER": "other"}) @patch("lms.djangoapps.mfe_api.views.configuration_helpers") @ddt.data( [{}, {}, {}], - [{"logo": "logo.jpg"}, {}, {"logo": "logo.jpg"}], - [{}, {"logo": "logo_mymfe.jpg"}, {"logo": "logo_mymfe.jpg"}], - [{"logo": "logo.jpg"}, {"logo": "logo_mymfe.jpg"}, {"logo": "logo_mymfe.jpg"}], - [{"logo": "logo.jpg", "other": "other"}, {"logo": "logo_mymfe.jpg"}, - {"logo": "logo_mymfe.jpg", "other": "other"}], + [{"EXAMPLE_VAR": "value"}, {}, {"EXAMPLE_VAR": "value"}], + [{}, {"EXAMPLE_VAR": "mymfe_value"}, {"EXAMPLE_VAR": "mymfe_value"}], + [{"EXAMPLE_VAR": "value"}, {"EXAMPLE_VAR": "mymfe_value"}, {"EXAMPLE_VAR": "mymfe_value"}], + [{"EXAMPLE_VAR": "value", "OTHER": "other"}, {"EXAMPLE_VAR": "mymfe_value"}, + {"EXAMPLE_VAR": "mymfe_value", "OTHER": "other"}], ) @ddt.unpack - def test_get_mfe_config_with_queryparams_other_cases( + def test_get_mfe_config_with_queryparam_multiple_configs( self, mfe_config, mfe_config_mymfe, expected_response, configuration_helpers_mock ): - """_summary_ + """Test the get mfe config with a query param and different settings in mfe_config and mfe_config_mfe inside + the site configuration to test that the merge of the configurations is done correctly and mymfe config take + precedence. - Args: - configuration_helpers_mock (_type_): _description_ - """ + In the ddt data the following structure is being passed: + [mfe_config, mfe_config_mymfe, expected_response] + Expected result: + - The get_value method of the configuration_helpers in the views is called twice, once with the + parameters ("MFE_CONFIG", {}) and once with the parameters ("MFE_CONFIG_MYMFE", {}). + - The json of the response is the expected_response passed by ddt.data. + """ configuration_helpers_mock.get_value.side_effect = [mfe_config, mfe_config_mymfe] response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") @@ -91,8 +99,9 @@ def test_404_get_mfe_config(self, configuration_helpers_mock): """Test the 404 not found response from get mfe config. Expected result: - - Response status code equal to 404 + - The get_value method of configuration_helpers is not called. + - The status of the response of the request is a HTTP_404_NOT_FOUND. """ response = self.client.get(self.mfe_config_api_url) configuration_helpers_mock.get_value.assert_not_called() - assert response.status_code == status.HTTP_404_NOT_FOUND + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) From f6f66d0f391e0459ef9cf06e8f0e425d834bc5d0 Mon Sep 17 00:00:00 2001 From: Maria Fernanda Magallanes Zubillaga Date: Mon, 4 Jul 2022 17:58:16 -0400 Subject: [PATCH 08/10] refactor!: change the api naming and url --- .github/workflows/pylint-checks.yml | 2 +- .github/workflows/unit-test-shards.json | 2 +- lms/djangoapps/mfe_api/urls.py | 10 ---------- .../{mfe_api => mfe_config_api}/__init__.py | 0 .../{mfe_api => mfe_config_api}/tests/__init__.py | 0 .../{mfe_api => mfe_config_api}/tests/test_views.py | 12 ++++++------ lms/djangoapps/mfe_config_api/urls.py | 10 ++++++++++ lms/djangoapps/{mfe_api => mfe_config_api}/views.py | 8 ++++---- lms/envs/common.py | 10 +++++----- lms/envs/test.py | 2 +- lms/urls.py | 2 +- 11 files changed, 29 insertions(+), 29 deletions(-) delete mode 100644 lms/djangoapps/mfe_api/urls.py rename lms/djangoapps/{mfe_api => mfe_config_api}/__init__.py (100%) rename lms/djangoapps/{mfe_api => mfe_config_api}/tests/__init__.py (100%) rename lms/djangoapps/{mfe_api => mfe_config_api}/tests/test_views.py (91%) create mode 100644 lms/djangoapps/mfe_config_api/urls.py rename lms/djangoapps/{mfe_api => mfe_config_api}/views.py (89%) diff --git a/.github/workflows/pylint-checks.yml b/.github/workflows/pylint-checks.yml index 43677f6bc10d..9494fa60f07c 100644 --- a/.github/workflows/pylint-checks.yml +++ b/.github/workflows/pylint-checks.yml @@ -17,7 +17,7 @@ jobs: - module-name: lms-1 path: "lms/djangoapps/badges/ lms/djangoapps/branding/ lms/djangoapps/bulk_email/ lms/djangoapps/bulk_enroll/ lms/djangoapps/bulk_user_retirement/ lms/djangoapps/ccx/ lms/djangoapps/certificates/ lms/djangoapps/commerce/ lms/djangoapps/course_api/ lms/djangoapps/course_blocks/ lms/djangoapps/course_home_api/ lms/djangoapps/course_wiki/ lms/djangoapps/coursewarehistoryextended/ lms/djangoapps/debug/ lms/djangoapps/courseware/ lms/djangoapps/course_goals/ lms/djangoapps/rss_proxy/ lms/djangoapps/save_for_later/" - module-name: lms-2 - path: "lms/djangoapps/gating/ lms/djangoapps/grades/ lms/djangoapps/instructor/ lms/djangoapps/instructor_analytics/ lms/djangoapps/discussion/ lms/djangoapps/edxnotes/ lms/djangoapps/email_marketing/ lms/djangoapps/experiments/ lms/djangoapps/instructor_task/ lms/djangoapps/learner_dashboard/ lms/djangoapps/lms_initialization/ lms/djangoapps/lms_xblock/ lms/djangoapps/lti_provider/ lms/djangoapps/mailing/ lms/djangoapps/mobile_api/ lms/djangoapps/monitoring/ lms/djangoapps/ora_staff_grader/ lms/djangoapps/program_enrollments/ lms/djangoapps/rss_proxy lms/djangoapps/static_template_view/ lms/djangoapps/staticbook/ lms/djangoapps/support/ lms/djangoapps/survey/ lms/djangoapps/teams/ lms/djangoapps/tests/ lms/djangoapps/user_tours/ lms/djangoapps/verify_student/ lms/djangoapps/mfe_api/ lms/envs/ lms/lib/ lms/tests.py" + path: "lms/djangoapps/gating/ lms/djangoapps/grades/ lms/djangoapps/instructor/ lms/djangoapps/instructor_analytics/ lms/djangoapps/discussion/ lms/djangoapps/edxnotes/ lms/djangoapps/email_marketing/ lms/djangoapps/experiments/ lms/djangoapps/instructor_task/ lms/djangoapps/learner_dashboard/ lms/djangoapps/lms_initialization/ lms/djangoapps/lms_xblock/ lms/djangoapps/lti_provider/ lms/djangoapps/mailing/ lms/djangoapps/mobile_api/ lms/djangoapps/monitoring/ lms/djangoapps/ora_staff_grader/ lms/djangoapps/program_enrollments/ lms/djangoapps/rss_proxy lms/djangoapps/static_template_view/ lms/djangoapps/staticbook/ lms/djangoapps/support/ lms/djangoapps/survey/ lms/djangoapps/teams/ lms/djangoapps/tests/ lms/djangoapps/user_tours/ lms/djangoapps/verify_student/ lms/djangoapps/mfe_config_api/ lms/envs/ lms/lib/ lms/tests.py" - module-name: openedx-1 path: "openedx/core/types/ openedx/core/djangoapps/ace_common/ openedx/core/djangoapps/agreements/ openedx/core/djangoapps/api_admin/ openedx/core/djangoapps/auth_exchange/ openedx/core/djangoapps/bookmarks/ openedx/core/djangoapps/cache_toolbox/ openedx/core/djangoapps/catalog/ openedx/core/djangoapps/ccxcon/ openedx/core/djangoapps/commerce/ openedx/core/djangoapps/common_initialization/ openedx/core/djangoapps/common_views/ openedx/core/djangoapps/config_model_utils/ openedx/core/djangoapps/content/ openedx/core/djangoapps/content_libraries/ openedx/core/djangoapps/contentserver/ openedx/core/djangoapps/cookie_metadata/ openedx/core/djangoapps/cors_csrf/ openedx/core/djangoapps/course_apps/ openedx/core/djangoapps/course_date_signals/ openedx/core/djangoapps/course_groups/ openedx/core/djangoapps/courseware_api/ openedx/core/djangoapps/crawlers/ openedx/core/djangoapps/credentials/ openedx/core/djangoapps/credit/ openedx/core/djangoapps/dark_lang/ openedx/core/djangoapps/debug/ openedx/core/djangoapps/demographics/ openedx/core/djangoapps/discussions/ openedx/core/djangoapps/django_comment_common/ openedx/core/djangoapps/embargo/ openedx/core/djangoapps/enrollments/ openedx/core/djangoapps/external_user_ids/ openedx/core/djangoapps/zendesk_proxy/ openedx/core/djangolib/ openedx/core/lib/ openedx/core/tests/ openedx/core/djangoapps/course_live/" - module-name: openedx-2 diff --git a/.github/workflows/unit-test-shards.json b/.github/workflows/unit-test-shards.json index c63ce0f97c67..81a618f1e687 100644 --- a/.github/workflows/unit-test-shards.json +++ b/.github/workflows/unit-test-shards.json @@ -71,7 +71,7 @@ "lms/djangoapps/tests/", "lms/djangoapps/user_tours/", "lms/djangoapps/verify_student/", - "lms/djangoapps/mfe_api/", + "lms/djangoapps/mfe_config_api/", "lms/envs/", "lms/lib/", "lms/tests.py" diff --git a/lms/djangoapps/mfe_api/urls.py b/lms/djangoapps/mfe_api/urls.py deleted file mode 100644 index 5cc5152345bb..000000000000 --- a/lms/djangoapps/mfe_api/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -""" URLs configuration for the mfe api.""" - -from django.urls import path - -from lms.djangoapps.mfe_api.views import MFEConfigView - -app_name = 'mfe_api' -urlpatterns = [ - path('v1/config', MFEConfigView.as_view(), name='config'), -] diff --git a/lms/djangoapps/mfe_api/__init__.py b/lms/djangoapps/mfe_config_api/__init__.py similarity index 100% rename from lms/djangoapps/mfe_api/__init__.py rename to lms/djangoapps/mfe_config_api/__init__.py diff --git a/lms/djangoapps/mfe_api/tests/__init__.py b/lms/djangoapps/mfe_config_api/tests/__init__.py similarity index 100% rename from lms/djangoapps/mfe_api/tests/__init__.py rename to lms/djangoapps/mfe_config_api/tests/__init__.py diff --git a/lms/djangoapps/mfe_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py similarity index 91% rename from lms/djangoapps/mfe_api/tests/test_views.py rename to lms/djangoapps/mfe_config_api/tests/test_views.py index 8084f563a1d3..4e57907f0686 100644 --- a/lms/djangoapps/mfe_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -17,10 +17,10 @@ class MFEConfigTestCase(APITestCase): Test the use case that exposes the site configuration with the mfe api. """ def setUp(self): - self.mfe_config_api_url = reverse("mfe_api:config") + self.mfe_config_api_url = reverse("mfe_config_api:config") return super().setUp() - @patch("lms.djangoapps.mfe_api.views.configuration_helpers") + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") def test_get_mfe_config(self, configuration_helpers_mock): """Test the get mfe config from site configuration with the mfe api. @@ -37,7 +37,7 @@ def test_get_mfe_config(self, configuration_helpers_mock): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json(), {"EXAMPLE_VAR": "value"}) - @patch("lms.djangoapps.mfe_api.views.configuration_helpers") + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") def test_get_mfe_config_with_queryparam(self, configuration_helpers_mock): """Test the get mfe config with a query param from site configuration. @@ -56,7 +56,7 @@ def test_get_mfe_config_with_queryparam(self, configuration_helpers_mock): configuration_helpers_mock.get_value.assert_has_calls(calls) self.assertEqual(response.json(), {"EXAMPLE_VAR": "mymfe_value", "OTHER": "other"}) - @patch("lms.djangoapps.mfe_api.views.configuration_helpers") + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") @ddt.data( [{}, {}, {}], [{"EXAMPLE_VAR": "value"}, {}, {"EXAMPLE_VAR": "value"}], @@ -93,8 +93,8 @@ def test_get_mfe_config_with_queryparam_multiple_configs( configuration_helpers_mock.get_value.assert_has_calls(calls) self.assertEqual(response.json(), expected_response) - @patch("lms.djangoapps.mfe_api.views.configuration_helpers") - @override_settings(ENABLE_MFE_API=False) + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") + @override_settings(ENABLE_MFE_CONFIG_API=False) def test_404_get_mfe_config(self, configuration_helpers_mock): """Test the 404 not found response from get mfe config. diff --git a/lms/djangoapps/mfe_config_api/urls.py b/lms/djangoapps/mfe_config_api/urls.py new file mode 100644 index 000000000000..8f63406a9afd --- /dev/null +++ b/lms/djangoapps/mfe_config_api/urls.py @@ -0,0 +1,10 @@ +""" URLs configuration for the mfe api.""" + +from django.urls import path + +from lms.djangoapps.mfe_config_api.views import MFEConfigView + +app_name = 'mfe_config_api' +urlpatterns = [ + path('', MFEConfigView.as_view(), name='config'), +] diff --git a/lms/djangoapps/mfe_api/views.py b/lms/djangoapps/mfe_config_api/views.py similarity index 89% rename from lms/djangoapps/mfe_api/views.py rename to lms/djangoapps/mfe_config_api/views.py index db874c8479c5..932109b728a8 100644 --- a/lms/djangoapps/mfe_api/views.py +++ b/lms/djangoapps/mfe_config_api/views.py @@ -17,12 +17,12 @@ class MFEConfigView(APIView): Provides an API endpoint to get the MFE_CONFIG from site configuration. """ - @method_decorator(cache_page(settings.MFE_API_CONFIG_CACHE_TIMEOUT)) + @method_decorator(cache_page(settings.MFE_CONFIG_API_CACHE_TIMEOUT)) def get(self, request): """ - GET /api/mfe/v1/config + GET /api/v1/mfe_config or - GET /api/mfe/v1/config?mfe=name_of_mfe + GET /api/v1/mfe_config?mfe=name_of_mfe **GET Response Values** ``` @@ -40,7 +40,7 @@ def get(self, request): ``` """ - if not settings.ENABLE_MFE_API: + if not settings.ENABLE_MFE_CONFIG_API: return HttpResponseNotFound() mfe_config = configuration_helpers.get_value('MFE_CONFIG', {}) diff --git a/lms/envs/common.py b/lms/envs/common.py index 8befc45934a7..631dc57a2d3a 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -3253,7 +3253,7 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring 'blockstore.apps.bundles', # MFE API - 'lms.djangoapps.mfe_api', + 'lms.djangoapps.mfe_config_api', ] ######################### CSRF ######################################### @@ -5143,7 +5143,7 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring PERSONALIZED_RECOMMENDATION_COOKIE_NAME = 'edx-user-personalized-recommendation' -# .. toggle_name: ENABLE_MFE_API +# .. toggle_name: ENABLE_MFE_CONFIG_API # .. toggle_implementation: DjangoSetting # .. toggle_default: False # .. toggle_description: Set to True to enable MFE Config REST API. This is disabled by @@ -5153,10 +5153,10 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring # .. toggle_target_removal_date: None # .. toggle_warnings: None # .. toggle_tickets: None -ENABLE_MFE_API = False +ENABLE_MFE_CONFIG_API = False -# .. setting_name: MFE_API_CONFIG_CACHE_TIMEOUT +# .. setting_name: MFE_CONFIG_API_CACHE_TIMEOUT # .. setting_default: 60*5 # .. setting_description: The MFE_CONFIG site configuration will be cached during the # specified time -MFE_API_CONFIG_CACHE_TIMEOUT = 60 * 5 +MFE_CONFIG_API_CACHE_TIMEOUT = 60 * 5 diff --git a/lms/envs/test.py b/lms/envs/test.py index 8d2f65ca3b6b..8164e8abb2ad 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -649,4 +649,4 @@ } ################## MFE API #################### -ENABLE_MFE_API = True +ENABLE_MFE_CONFIG_API = True diff --git a/lms/urls.py b/lms/urls.py index 974e14bc2787..c7669bf9ea7f 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -1025,5 +1025,5 @@ # MFE API urls urlpatterns += [ - path('api/mfe/', include(('lms.djangoapps.mfe_api.urls', 'lms.djangoapps.mfe_api'), namespace='mfe_api')) + path('api/v1/mfe_config', include(('lms.djangoapps.mfe_config_api.urls', 'lms.djangoapps.mfe_config_api'), namespace='mfe_config_api')) ] From d6a30882d157474f9c76ccb6731470dd2ab7cc5e Mon Sep 17 00:00:00 2001 From: Maria Fernanda Magallanes Zubillaga Date: Mon, 4 Jul 2022 19:32:01 -0400 Subject: [PATCH 09/10] docs: add the ADR --- .../docs/decisions/0001-mfe-config-api.rst | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 lms/djangoapps/mfe_config_api/docs/decisions/0001-mfe-config-api.rst diff --git a/lms/djangoapps/mfe_config_api/docs/decisions/0001-mfe-config-api.rst b/lms/djangoapps/mfe_config_api/docs/decisions/0001-mfe-config-api.rst new file mode 100644 index 000000000000..ebff2419c64b --- /dev/null +++ b/lms/djangoapps/mfe_config_api/docs/decisions/0001-mfe-config-api.rst @@ -0,0 +1,70 @@ +0001 MFE CONFIG API +#################### + +Status +****** + +Accepted + +Context +******* + +Currently, MFE settings are set via command line environment variables or an .env file that is read during the build process, causing the operators to rebuild mfes each time when any variables are changed. The creation of the ``mfe_config_api`` allows configuration at runtime and avoids rebuilds. +`MFE Configuration during Runtime`_. + +Decision +******** + +- A lightweight API will be created that returns the mfe configuration variables from the site configuration. +- The API will be enabled or disabled using the setting ``ENABLE_MFE_CONFIG_API``. +- The API will take the mfe configuration in the ``MFE_CONFIG`` keyset in the site configuration (admin > site configuration > your domain). +- This API allows to consult the configurations by specific MFE. Making a request like ``api/v1/mfe_config?mfe=mymfe`` will return the configuration defined in ``MFE_CONFIG_MYMFE`` merged with the ``MFE_CONFIG`` configuration. +- The API will have a mechanism to cache the response with ``MFE_CONFIG_API_CACHE_TIMEOUT`` variable. +- The API will live in lms/djangoapps because this is not something Studio needs to serve and it is a lightweight API. `PR Discussion`_ +- The API will not require authentication or authorization. +- The API request and response will be like: + +Request:: + + GET http://lms.base.com/api/v1/mfe_config?mfe=learning + +Response:: + + { + "BASE_URL": "https://name_of_mfe.example.com", + "LANGUAGE_PREFERENCE_COOKIE_NAME": "example-language-preference", + "CREDENTIALS_BASE_URL": "https://credentials.example.com", + "DISCOVERY_API_BASE_URL": "https://discovery.example.com", + "LMS_BASE_URL": "https://courses.example.com", + "LOGIN_URL": "https://courses.example.com/login", + "LOGOUT_URL": "https://courses.example.com/logout", + "STUDIO_BASE_URL": "https://studio.example.com", + "LOGO_URL": "https://courses.example.com/logo.png" + + } + +Consequences +************ + +- We have to change all the mfes so that they take the information from the API. `Issue MFE runtime configuration in frontend-wg`_ +- Initialize the MFE could have a delay due to the HTTP method. +- `Site configuration is going to be deprecated`_ so later we have to take the configuration from django settings. +- The operator is responsible for configuring the settings in site configuration. +- We can have duplicate keys in site configuration (example: we can have a logo definition for each mfe). +- If the request is made from a domain that does not have a site configuration, it returns an empty json. + +Rejected Alternatives +********************** + +- It was not made as a plugin or IDA because it is a lightweight implementation `PR Discussion`_ + +References +********** + +.. _MFE Configuration during Runtime: https://docs.google.com/document/d/1-FHIQmyeQZu3311x8eYUNMru4JX7Yb3UlqjmJxvM8do/edit?usp=sharing + +.. _PR Discussion: https://github.com/openedx/edx-platform/pull/30473#issuecomment-1146176151 + +.. _Site configuration is going to be deprecated: https://github.com/openedx/platform-roadmap/issues/21 + +.. _Issue MFE runtime configuration in frontend-wg: https://github.com/openedx/frontend-wg/issues/103 From f2488b10a6f7f4faca8cb8877ba95d2a386822a0 Mon Sep 17 00:00:00 2001 From: Maria Fernanda Magallanes Zubillaga Date: Fri, 8 Jul 2022 01:15:04 -0400 Subject: [PATCH 10/10] feat: make it compatible with django settings --- .../docs/decisions/0001-mfe-config-api.rst | 12 +++--- .../mfe_config_api/tests/test_views.py | 42 +++++++++++++++---- lms/djangoapps/mfe_config_api/views.py | 5 ++- lms/envs/common.py | 23 +++++++++- lms/envs/test.py | 10 +++++ 5 files changed, 76 insertions(+), 16 deletions(-) diff --git a/lms/djangoapps/mfe_config_api/docs/decisions/0001-mfe-config-api.rst b/lms/djangoapps/mfe_config_api/docs/decisions/0001-mfe-config-api.rst index ebff2419c64b..d26a40dc9ae6 100644 --- a/lms/djangoapps/mfe_config_api/docs/decisions/0001-mfe-config-api.rst +++ b/lms/djangoapps/mfe_config_api/docs/decisions/0001-mfe-config-api.rst @@ -15,9 +15,9 @@ Currently, MFE settings are set via command line environment variables or an .en Decision ******** -- A lightweight API will be created that returns the mfe configuration variables from the site configuration. +- A lightweight API will be created that returns the mfe configuration variables from the site configuration or django settings. `PR Discussion about django settings`_ - The API will be enabled or disabled using the setting ``ENABLE_MFE_CONFIG_API``. -- The API will take the mfe configuration in the ``MFE_CONFIG`` keyset in the site configuration (admin > site configuration > your domain). +- The API will take the mfe configuration in the ``MFE_CONFIG`` keyset in the site configuration (admin > site configuration > your domain) or in django settings. - This API allows to consult the configurations by specific MFE. Making a request like ``api/v1/mfe_config?mfe=mymfe`` will return the configuration defined in ``MFE_CONFIG_MYMFE`` merged with the ``MFE_CONFIG`` configuration. - The API will have a mechanism to cache the response with ``MFE_CONFIG_API_CACHE_TIMEOUT`` variable. - The API will live in lms/djangoapps because this is not something Studio needs to serve and it is a lightweight API. `PR Discussion`_ @@ -48,10 +48,10 @@ Consequences - We have to change all the mfes so that they take the information from the API. `Issue MFE runtime configuration in frontend-wg`_ - Initialize the MFE could have a delay due to the HTTP method. -- `Site configuration is going to be deprecated`_ so later we have to take the configuration from django settings. -- The operator is responsible for configuring the settings in site configuration. +- `Site configuration is going to be deprecated`_ so later we have to clean the code that uses site configuration. +- The operator is responsible for configuring the settings in site configuration or django settings. - We can have duplicate keys in site configuration (example: we can have a logo definition for each mfe). -- If the request is made from a domain that does not have a site configuration, it returns an empty json. +- If the request is made from a domain that does not have a site configuration, it returns django settings. Rejected Alternatives ********************** @@ -68,3 +68,5 @@ References .. _Site configuration is going to be deprecated: https://github.com/openedx/platform-roadmap/issues/21 .. _Issue MFE runtime configuration in frontend-wg: https://github.com/openedx/frontend-wg/issues/103 + +.. _PR Discussion about django settings: https://github.com/openedx/edx-platform/pull/30473#discussion_r916263245 diff --git a/lms/djangoapps/mfe_config_api/tests/test_views.py b/lms/djangoapps/mfe_config_api/tests/test_views.py index 4e57907f0686..35ad520d342e 100644 --- a/lms/djangoapps/mfe_config_api/tests/test_views.py +++ b/lms/djangoapps/mfe_config_api/tests/test_views.py @@ -5,6 +5,7 @@ from unittest.mock import call, patch import ddt +from django.conf import settings from django.test import override_settings from django.urls import reverse from rest_framework import status @@ -26,14 +27,14 @@ def test_get_mfe_config(self, configuration_helpers_mock): Expected result: - The get_value method of the configuration_helpers in the views is called once with the - parameters ("MFE_CONFIG", {}). + parameters ("MFE_CONFIG", getattr(settings, "MFE_CONFIG", {})). - The status of the response of the request is a HTTP_200_OK. - The json of the response of the request is equal to the mocked configuration. """ configuration_helpers_mock.get_value.return_value = {"EXAMPLE_VAR": "value"} response = self.client.get(self.mfe_config_api_url) - configuration_helpers_mock.get_value.assert_called_once_with("MFE_CONFIG", {}) + configuration_helpers_mock.get_value.assert_called_once_with("MFE_CONFIG", getattr(settings, "MFE_CONFIG", {})) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json(), {"EXAMPLE_VAR": "value"}) @@ -43,8 +44,9 @@ def test_get_mfe_config_with_queryparam(self, configuration_helpers_mock): Expected result: - The get_value method of the configuration_helpers in the views is called twice, once with the - parameters ("MFE_CONFIG", {}) and once with the parameters ("MFE_CONFIG_MYMFE", {}). - and one for get_value("MFE_CONFIG_MYMFE", {}). + parameters ("MFE_CONFIG", getattr(settings, "MFE_CONFIG", {})) and once with the parameters + ("MFE_CONFIG_MYMFE", getattr(settings, "MFE_CONFIG_MYMFE", {})). + and one for get_value("MFE_CONFIG_MYMFE", getattr(settings, "MFE_CONFIG_MYMFE", {})). - The json of the response is the merge of both mocked configurations. """ configuration_helpers_mock.get_value.side_effect = [{"EXAMPLE_VAR": "value", "OTHER": "other"}, @@ -52,7 +54,8 @@ def test_get_mfe_config_with_queryparam(self, configuration_helpers_mock): response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") self.assertEqual(response.status_code, status.HTTP_200_OK) - calls = [call("MFE_CONFIG", {}), call("MFE_CONFIG_MYMFE", {})] + calls = [call("MFE_CONFIG", getattr(settings, "MFE_CONFIG", {})), + call("MFE_CONFIG_MYMFE", getattr(settings, "MFE_CONFIG_MYMFE", {}))] configuration_helpers_mock.get_value.assert_has_calls(calls) self.assertEqual(response.json(), {"EXAMPLE_VAR": "mymfe_value", "OTHER": "other"}) @@ -82,17 +85,42 @@ def test_get_mfe_config_with_queryparam_multiple_configs( Expected result: - The get_value method of the configuration_helpers in the views is called twice, once with the - parameters ("MFE_CONFIG", {}) and once with the parameters ("MFE_CONFIG_MYMFE", {}). + parameters ("MFE_CONFIG", getattr(settings, "MFE_CONFIG", {})) and once with the parameters + ("MFE_CONFIG_MYMFE", getattr(settings, "MFE_CONFIG_MYMFE", {})). - The json of the response is the expected_response passed by ddt.data. """ configuration_helpers_mock.get_value.side_effect = [mfe_config, mfe_config_mymfe] response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") self.assertEqual(response.status_code, status.HTTP_200_OK) - calls = [call("MFE_CONFIG", {}), call("MFE_CONFIG_MYMFE", {})] + calls = [call("MFE_CONFIG", getattr(settings, "MFE_CONFIG", {})), + call("MFE_CONFIG_MYMFE", getattr(settings, "MFE_CONFIG_MYMFE", {}))] configuration_helpers_mock.get_value.assert_has_calls(calls) self.assertEqual(response.json(), expected_response) + def test_get_mfe_config_from_django_settings(self): + """Test that when there is no site configuration, the API takes the django settings. + + Expected result: + - The status of the response of the request is a HTTP_200_OK. + - The json response is equal to MFE_CONFIG in lms/envs/test.py""" + response = self.client.get(self.mfe_config_api_url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json(), getattr(settings, "MFE_CONFIG", {})) + + def test_get_mfe_config_with_queryparam_from_django_settings(self): + """Test that when there is no site configuration, the API with queryparam takes the django settings. + + Expected result: + - The status of the response of the request is a HTTP_200_OK. + - The json response is equal to MFE_CONFIG merged with MFE_CONFIG_MYMFE in lms/envs/test.py + """ + response = self.client.get(f"{self.mfe_config_api_url}?mfe=mymfe") + self.assertEqual(response.status_code, status.HTTP_200_OK) + expected_response = getattr(settings, "MFE_CONFIG", {}) + expected_response.update(getattr(settings, "MFE_CONFIG_MYMFE", {})) + self.assertEqual(response.json(), expected_response) + @patch("lms.djangoapps.mfe_config_api.views.configuration_helpers") @override_settings(ENABLE_MFE_CONFIG_API=False) def test_404_get_mfe_config(self, configuration_helpers_mock): diff --git a/lms/djangoapps/mfe_config_api/views.py b/lms/djangoapps/mfe_config_api/views.py index 932109b728a8..13f4e4955c41 100644 --- a/lms/djangoapps/mfe_config_api/views.py +++ b/lms/djangoapps/mfe_config_api/views.py @@ -43,9 +43,10 @@ def get(self, request): if not settings.ENABLE_MFE_CONFIG_API: return HttpResponseNotFound() - mfe_config = configuration_helpers.get_value('MFE_CONFIG', {}) + mfe_config = configuration_helpers.get_value('MFE_CONFIG', getattr(settings, 'MFE_CONFIG', {})) if request.query_params.get('mfe'): mfe = str(request.query_params.get('mfe')).upper() - mfe_config.update(configuration_helpers.get_value(f'MFE_CONFIG_{mfe}', {})) + mfe_config.update(configuration_helpers.get_value( + f'MFE_CONFIG_{mfe}', getattr(settings, f'MFE_CONFIG_{mfe}', {}))) return JsonResponse(mfe_config, status=status.HTTP_200_OK) diff --git a/lms/envs/common.py b/lms/envs/common.py index 631dc57a2d3a..a5662dd5e586 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -5146,7 +5146,7 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring # .. toggle_name: ENABLE_MFE_CONFIG_API # .. toggle_implementation: DjangoSetting # .. toggle_default: False -# .. toggle_description: Set to True to enable MFE Config REST API. This is disabled by +# .. toggle_description: Set to True to enable MFE Config API. This is disabled by # default. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2022-05-20 @@ -5155,8 +5155,27 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring # .. toggle_tickets: None ENABLE_MFE_CONFIG_API = False +# .. setting_name: MFE_CONFIG +# .. setting_implementation: DjangoSetting +# .. setting_default: {} +# .. setting_description: Is a configuration that will be exposed by the MFE Config API to be consumed by the mfes +# Example: { +# "BASE_URL": "https://name_of_mfe.example.com", +# "LANGUAGE_PREFERENCE_COOKIE_NAME": "example-language-preference", +# "CREDENTIALS_BASE_URL": "https://credentials.example.com", +# "DISCOVERY_API_BASE_URL": "https://discovery.example.com", +# "LMS_BASE_URL": "https://courses.example.com", +# "LOGIN_URL": "https://courses.example.com/login", +# "LOGOUT_URL": "https://courses.example.com/logout", +# "STUDIO_BASE_URL": "https://studio.example.com", +# "LOGO_URL": "https://courses.example.com/logo.png" +# } +# .. setting_use_cases: open_edx +# .. setting_creation_date: 2022-07-08 +MFE_CONFIG = {} + # .. setting_name: MFE_CONFIG_API_CACHE_TIMEOUT # .. setting_default: 60*5 -# .. setting_description: The MFE_CONFIG site configuration will be cached during the +# .. setting_description: The MFE Config API response will be cached during the # specified time MFE_CONFIG_API_CACHE_TIMEOUT = 60 * 5 diff --git a/lms/envs/test.py b/lms/envs/test.py index 8164e8abb2ad..08abdbdcf65d 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -650,3 +650,13 @@ ################## MFE API #################### ENABLE_MFE_CONFIG_API = True +MFE_CONFIG = { + "BASE_URL": "https://name_of_mfe.example.com", + "LANGUAGE_PREFERENCE_COOKIE_NAME": "example-language-preference", + "LOGO_URL": "https://courses.example.com/logo.png" +} + +MFE_CONFIG_MYMFE = { + "LANGUAGE_PREFERENCE_COOKIE_NAME": "mymfe-language-preference", + "LOGO_URL": "https://courses.example.com/mymfe-logo.png" +}