feat: Add support for using the discussions MFE UI instead of existing UI [BD-38] [TNL-9228] (#29285)

* feat: Add support for using the discussions MFE UI instead of existing UI

Adds a new course waffle flag that when set along with the discussions MFE URL shows the discussions MFE UI instead of the regular UI.

* test: add tests

* squash!: more consistent url name
This commit is contained in:
Kshitij Sobti
2021-11-23 09:56:25 +00:00
committed by GitHub
parent c12c3b0bf0
commit c8bd924e23
10 changed files with 141 additions and 10 deletions

View File

@@ -494,6 +494,7 @@ IDA_LOGOUT_URI_LIST = []
############################# MICROFRONTENDS ###################################
COURSE_AUTHORING_MICROFRONTEND_URL = None
DISCUSSIONS_MICROFRONTEND_URL = None
LIBRARY_AUTHORING_MICROFRONTEND_URL = None
############################# SOCIAL MEDIA SHARING #############################

View File

@@ -162,6 +162,9 @@ LIBRARY_AUTHORING_MICROFRONTEND_URL = 'http://localhost:3001'
################### FRONTEND APPLICATION COURSE AUTHORING ###################
COURSE_AUTHORING_MICROFRONTEND_URL = 'http://localhost:2001'
################### FRONTEND APPLICATION DISCUSSIONS ###################
DISCUSSIONS_MICROFRONTEND_URL = 'http://localhost:2002'
################################# DJANGO-REQUIRE ###############################
# Whether to run django-require in debug mode.

View File

@@ -12,15 +12,20 @@ import uuid
from unittest import mock
import ddt
from django.test import override_settings
from django.urls import reverse
from edx_toggles.toggles.testutils import override_waffle_flag
from opaque_keys.edx.keys import CourseKey
from web_fragments.fragment import Fragment
from xblock.field_data import DictFieldData
from lms.djangoapps.course_api.blocks.tests.helpers import deserialize_usage_key
from lms.djangoapps.courseware.module_render import get_module_for_descriptor_internal
from lms.djangoapps.courseware.tests.helpers import XModuleRenderingTestBase
from lms.djangoapps.discussion.toggles import ENABLE_DISCUSSIONS_MFE
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from xblock_discussion import DiscussionXBlock, loader
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import ItemFactory, ToyCourseFactory
@@ -40,7 +45,7 @@ class TestDiscussionXBlock(XModuleRenderingTestBase):
"""
super().setUp()
self.patchers = []
self.course_id = "test_course"
self.course_id = CourseKey.from_string("course-v1:test+test+test_course")
self.runtime = self.new_module_runtime()
self.runtime.modulestore = mock.Mock()
@@ -220,7 +225,7 @@ class TestTemplates(TestDiscussionXBlock):
) as has_perm:
actual_permission = self.block.has_permission("test_permission")
assert actual_permission == permission_canary
has_perm.assert_called_once_with(self.django_user_canary, 'test_permission', 'test_course')
has_perm.assert_called_once_with(self.django_user_canary, 'test_permission', self.course_id)
def test_studio_view(self):
"""Test for studio view."""
@@ -304,6 +309,35 @@ class TestXBlockInCourse(SharedModuleStoreTestCase):
assert 'data-user-create-comment="false"' in html
assert 'data-user-create-subcomment="false"' in html
@override_settings(DISCUSSIONS_MICROFRONTEND_URL="http://test.url")
@override_waffle_flag(ENABLE_DISCUSSIONS_MFE, True)
def test_embed_mfe_in_course(self):
"""
Test that the xblock embeds the MFE UI when the flag is enabled
"""
discussion_xblock = get_module_for_descriptor_internal(
user=self.user,
descriptor=self.discussion,
student_data=mock.Mock(name='student_data'),
course_id=self.course.id,
track_function=mock.Mock(name='track_function'),
xqueue_callback_url_prefix=mock.Mock(name='xqueue_callback_url_prefix'),
request_token='request_token',
)
fragment = discussion_xblock.render('student_view')
html = fragment.content
self.assertInHTML(
"""
<iframe
id='discussions-mfe-tab-embed'
title='Discussions'
src='http://test.url/discussions/edX/toy/2012_Fall/topics/test_discussion_xblock_id'
/>
""",
html,
)
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_discussion_render_successfully_with_orphan_parent(self, default_store):
"""
@@ -400,11 +434,12 @@ class TestXBlockQueryLoad(SharedModuleStoreTestCase):
discussion_target='Target Discussion',
))
# 3 queries are required to do first discussion xblock render:
# 4 queries are required to do first discussion xblock render:
# * waffle_utils_wafflecourseoverridemodel
# * waffle_flag
# * django_comment_client_role
# * django_comment_client_permission
# * lms_xblock_xblockasidesconfig
num_queries = 2
num_queries = 4
for discussion in discussions:
discussion_xblock = get_module_for_descriptor_internal(
user=user,
@@ -418,8 +453,9 @@ class TestXBlockQueryLoad(SharedModuleStoreTestCase):
with self.assertNumQueries(num_queries):
fragment = discussion_xblock.render('student_view')
# Permissions are cached, so no queries required for subsequent renders
num_queries = 0
# Permissions are cached, so only 1 query required for subsequent renders
# to check the waffle flag
num_queries = 1
html = fragment.content
assert 'data-user-create-comment="false"' in html

View File

@@ -9,9 +9,10 @@ from unittest.mock import Mock, patch
import ddt
import pytest
from django.test import RequestFactory, TestCase
from django.test import RequestFactory, TestCase, override_settings
from django.urls import reverse
from edx_django_utils.cache import RequestCache
from edx_toggles.toggles.testutils import override_waffle_flag
from opaque_keys.edx.keys import CourseKey
from pytz import UTC
@@ -26,6 +27,7 @@ from lms.djangoapps.discussion.django_comment_client.constants import TYPE_ENTRY
from lms.djangoapps.discussion.django_comment_client.tests.factories import RoleFactory
from lms.djangoapps.discussion.django_comment_client.tests.unicode import UnicodeTestMixin
from lms.djangoapps.discussion.django_comment_client.tests.utils import config_course_discussions, topic_name_to_id
from lms.djangoapps.discussion.toggles import ENABLE_DISCUSSIONS_MFE
from lms.djangoapps.teams.tests.factories import CourseTeamFactory
from openedx.core.djangoapps.course_groups import cohorts
from openedx.core.djangoapps.course_groups.cohorts import set_course_cohorted
@@ -53,6 +55,7 @@ from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_MODULESTORE, ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, ToyCourseFactory
from xmodule.tabs import CourseTabList
class DictionaryTestCase(TestCase):
@@ -1198,6 +1201,7 @@ class JsonResponseTestCase(TestCase, UnicodeTestMixin):
assert reparsed == text
@ddt.ddt
class DiscussionTabTestCase(ModuleStoreTestCase):
""" Test visibility of the discussion tab. """
@@ -1230,6 +1234,20 @@ class DiscussionTabTestCase(ModuleStoreTestCase):
with self.settings(FEATURES={'CUSTOM_COURSES_EDX': True}):
assert not self.discussion_tab_present(self.enrolled_user)
@override_settings(DISCUSSIONS_MICROFRONTEND_URL="http://test.url")
@ddt.data(
(True, 'http://test.url/discussions/{}/'),
(False, '/courses/{}/discussion/forum/'),
)
@ddt.unpack
def test_tab_with_mfe_flag(self, mfe_enabled, tab_link):
"""
Tests that the correct link is used for the MFE tab
"""
discussion_tab = CourseTabList.get_tab_by_type(self.course.tabs, 'discussion')
with override_waffle_flag(ENABLE_DISCUSSIONS_MFE, mfe_enabled):
assert discussion_tab.link_func(self.course, reverse) == tab_link.format(self.course.id)
class IsCommentableDividedTestCase(ModuleStoreTestCase):
"""

View File

@@ -8,6 +8,7 @@ from django.utils.translation import gettext_noop
import lms.djangoapps.discussion.django_comment_client.utils as utils
from lms.djangoapps.courseware.tabs import EnrolledTab
from lms.djangoapps.discussion.toggles import ENABLE_DISCUSSIONS_MFE
from openedx.features.lti_course_tab.tab import DiscussionLtiCourseTab
from xmodule.tabs import TabFragmentViewMixin
@@ -27,6 +28,19 @@ class DiscussionTab(TabFragmentViewMixin, EnrolledTab):
body_class = 'discussion'
online_help_token = 'discussions'
@property
def link_func(self):
""" Returns a function that returns the course tab's URL. """
_link_func = super().link_func
def link_func(course, reverse_func):
""" Returns a function that returns the course tab's URL. """
if ENABLE_DISCUSSIONS_MFE.is_enabled(course.id) and settings.DISCUSSIONS_MICROFRONTEND_URL:
return f"{settings.DISCUSSIONS_MICROFRONTEND_URL}/discussions/{course.id}/"
return _link_func(course, reverse_func)
return link_func
@classmethod
def is_enabled(cls, course, user=None):
if not super().is_enabled(course, user):

View File

@@ -0,0 +1,15 @@
"""
Discussions feature toggles
"""
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
WAFFLE_FLAG_NAMESPACE = "discussions"
# .. toggle_name: discussions.enable_discussions_mfe
# .. toggle_implementation: CourseWaffleFlag
# .. toggle_default: False
# .. toggle_description: Waffle flag to use the new MFE experience for discussions in the course tab and in-context
# .. toggle_use_cases: temporary, open_edx
# .. toggle_creation_date: 2021-11-05
# .. toggle_target_removal_date: 2022-03-05
ENABLE_DISCUSSIONS_MFE = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'enable_discussions_mfe', __name__)

View File

@@ -45,6 +45,7 @@ from lms.djangoapps.discussion.django_comment_client.utils import (
strip_none,
)
from lms.djangoapps.discussion.exceptions import TeamDiscussionHiddenFromUserException
from lms.djangoapps.discussion.toggles import ENABLE_DISCUSSIONS_MFE
from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context
from lms.djangoapps.teams import api as team_api
from openedx.core.djangoapps.discussions.utils import (
@@ -56,6 +57,7 @@ from openedx.core.djangoapps.discussions.utils import (
from openedx.core.djangoapps.django_comment_common.models import CourseDiscussionSettings
from openedx.core.djangoapps.django_comment_common.utils import ThreadContext
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from openedx.core.djangolib.markup import HTML
from openedx.features.course_duration_limits.access import generate_course_expired_fragment
from xmodule.modulestore.django import modulestore
@@ -717,8 +719,24 @@ class DiscussionBoardFragmentView(EdxFragmentView):
Returns:
Fragment: The fragment representing the discussion board
"""
course_key = CourseKey.from_string(course_id)
if ENABLE_DISCUSSIONS_MFE.is_enabled(course_key) and settings.DISCUSSIONS_MICROFRONTEND_URL:
fragment = Fragment(
HTML(
"<iframe id='discussions-mfe-tab-embed' src='{src}'></iframe>"
).format(src=f"{settings.DISCUSSIONS_MICROFRONTEND_URL}/discussions/{course_id}/")
)
fragment.add_css(
"""
#discussions-mfe-tab-embed {
width: 100%;
min-height: 800px;
border: none;
}
"""
)
return fragment
try:
course_key = CourseKey.from_string(course_id)
base_context = _create_base_discussion_view_context(request, course_key)
# Note:
# After the thread is rendered in this fragment, an AJAX

View File

@@ -4683,6 +4683,11 @@ PROGRAM_CONSOLE_MICROFRONTEND_URL = None
# .. setting_description: Base URL of the micro-frontend-based courseware page.
# .. setting_warning: Also set site's courseware.courseware_mfe waffle flag.
LEARNING_MICROFRONTEND_URL = None
# .. setting_name: DISCUSSIONS_MICROFRONTEND_URL
# .. setting_default: None
# .. setting_description: Base URL of the micro-frontend-based dicussions page.
# .. setting_warning: Also set site's courseware.discussions_mfe waffle flag.
DISCUSSIONS_MICROFRONTEND_URL = None
############### Settings for the ace_common plugin #################
ACE_ENABLED_CHANNELS = ['django_email']

View File

@@ -338,6 +338,9 @@ ACCOUNT_MICROFRONTEND_URL = 'http://localhost:1997'
AUTHN_MICROFRONTEND_URL = 'http://localhost:1999'
AUTHN_MICROFRONTEND_DOMAIN = 'localhost:1999'
################### FRONTEND APPLICATION DISCUSSIONS ###################
DISCUSSIONS_MICROFRONTEND_URL = 'http://localhost:2002'
############## Docker based devstack settings #######################
FEATURES.update({

View File

@@ -4,16 +4,19 @@ Discussion XBlock
import logging
import urllib
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse
from django.utils.translation import get_language_bidi
from web_fragments.fragment import Fragment
from xblock.completable import XBlockCompletionMode
from xblock.core import XBlock
from xblock.fields import Scope, String, UNIQUE_ID
from web_fragments.fragment import Fragment
from xblockutils.resources import ResourceLoader
from xblockutils.studio_editable import StudioEditableXBlockMixin
from lms.djangoapps.discussion.toggles import ENABLE_DISCUSSIONS_MFE
from openedx.core.djangolib.markup import HTML, Text
from openedx.core.lib.xblock_builtin import get_css_dependencies, get_js_dependencies
from xmodule.xml_module import XmlParserMixin
@@ -166,6 +169,21 @@ class DiscussionXBlock(XBlock, StudioEditableXBlockMixin, XmlParserMixin): # li
Renders student view for LMS.
"""
fragment = Fragment()
if ENABLE_DISCUSSIONS_MFE.is_enabled(self.course_key) and settings.DISCUSSIONS_MICROFRONTEND_URL:
url = f"{settings.DISCUSSIONS_MICROFRONTEND_URL}/discussions/{self.course_key}/topics/{self.discussion_id}"
fragment.add_content(HTML(
"<iframe id='discussions-mfe-tab-embed' src='{src}' title='{title}'></iframe>"
).format(src=url, title=_("Discussions")))
fragment.add_css(
"""
#discussions-mfe-tab-embed {
width: 100%;
height: 800px;
border: none;
}
"""
)
return fragment
self.add_resource_urls(fragment)