feat: banner for staff users that displays a message for testing the new MFE experience (#29698)
Adds a new banner allowing staff users to preview and switch between the new and legacy forum experience.
This commit is contained in:
@@ -5,10 +5,13 @@ Tests for the django comment client integration models
|
||||
|
||||
from django.test.testcases import TestCase
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from xmodule.modulestore.tests.django_utils import (
|
||||
TEST_DATA_MIXED_MODULESTORE,
|
||||
ModuleStoreTestCase
|
||||
)
|
||||
from xmodule.modulestore.tests.factories import ToyCourseFactory
|
||||
|
||||
import openedx.core.djangoapps.django_comment_common.models as models
|
||||
from xmodule.modulestore.tests.django_utils import TEST_DATA_MIXED_MODULESTORE, ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import ToyCourseFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
class RoleClassTestCase(ModuleStoreTestCase):
|
||||
|
||||
@@ -1234,20 +1234,6 @@ 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/{}/'),
|
||||
(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):
|
||||
"""
|
||||
|
||||
@@ -5,17 +5,17 @@ Utilities for tests within the django_comment_client module.
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||
from common.djangoapps.util.testing import UrlResetMixin
|
||||
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
|
||||
from openedx.core.djangoapps.django_comment_common.models import CourseDiscussionSettings
|
||||
from openedx.core.djangoapps.django_comment_common.models import ForumsConfig, Role
|
||||
from openedx.core.djangoapps.django_comment_common.models import CourseDiscussionSettings, ForumsConfig, Role
|
||||
from openedx.core.djangoapps.django_comment_common.utils import seed_permissions_roles
|
||||
from openedx.core.lib.teams_config import TeamsConfig
|
||||
from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
class ForumsEnableMixin:
|
||||
|
||||
@@ -5,13 +5,11 @@ Views handling read (GET) requests for the Discussion tab and inline discussions
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext_noop
|
||||
from xmodule.tabs import TabFragmentViewMixin
|
||||
|
||||
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.core.djangoapps.discussions.url_helpers import get_discussions_mfe_url
|
||||
from openedx.features.lti_course_tab.tab import DiscussionLtiCourseTab
|
||||
from xmodule.tabs import TabFragmentViewMixin # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
class DiscussionTab(TabFragmentViewMixin, EnrolledTab):
|
||||
@@ -29,20 +27,6 @@ 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. """
|
||||
mfe_url = get_discussions_mfe_url(course.id)
|
||||
if ENABLE_DISCUSSIONS_MFE.is_enabled(course.id) and mfe_url:
|
||||
return mfe_url
|
||||
return _link_func(course, reverse_func)
|
||||
|
||||
return link_func
|
||||
|
||||
@classmethod
|
||||
def is_enabled(cls, course, user=None):
|
||||
if not super().is_enabled(course, user):
|
||||
|
||||
@@ -6,6 +6,8 @@ Tests for discussion API permission logic
|
||||
import itertools
|
||||
|
||||
import ddt
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from lms.djangoapps.discussion.rest_api.permissions import (
|
||||
can_delete,
|
||||
@@ -16,8 +18,6 @@ from lms.djangoapps.discussion.rest_api.permissions import (
|
||||
from openedx.core.djangoapps.django_comment_common.comment_client.comment import Comment
|
||||
from openedx.core.djangoapps.django_comment_common.comment_client.thread import Thread
|
||||
from openedx.core.djangoapps.django_comment_common.comment_client.user import User
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
def _get_context(
|
||||
|
||||
@@ -5,12 +5,12 @@ Tests for Discussion REST API utils.
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from pytz import UTC
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory, CourseEnrollmentFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||
from lms.djangoapps.discussion.django_comment_client.tests.factories import RoleFactory
|
||||
from lms.djangoapps.discussion.rest_api.utils import discussion_open_for_user
|
||||
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
class DiscussionAPIUtilsTestCase(ModuleStoreTestCase):
|
||||
|
||||
@@ -7,9 +7,9 @@ import logging
|
||||
import uuid
|
||||
|
||||
import edx_api_doc_tools as apidocs
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.exceptions import BadRequest, ValidationError
|
||||
from django.shortcuts import get_object_or_404
|
||||
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
|
||||
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
@@ -20,6 +20,7 @@ from rest_framework.parsers import JSONParser
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.viewsets import ViewSet
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from common.djangoapps.util.file import store_uploaded_file
|
||||
from lms.djangoapps.course_goals.models import UserActivity
|
||||
@@ -33,7 +34,7 @@ from openedx.core.djangoapps.user_api.models import UserRetirementStatus
|
||||
from openedx.core.lib.api.authentication import BearerAuthentication, BearerAuthenticationAllowInactiveUser
|
||||
from openedx.core.lib.api.parsers import MergePatchParser
|
||||
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes
|
||||
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
from ..rest_api.api import (
|
||||
create_comment,
|
||||
create_thread,
|
||||
@@ -47,22 +48,18 @@ from ..rest_api.api import (
|
||||
get_thread_list,
|
||||
get_user_comments,
|
||||
update_comment,
|
||||
update_thread,
|
||||
update_thread
|
||||
)
|
||||
from ..rest_api.forms import (
|
||||
CommentGetForm,
|
||||
CommentListGetForm,
|
||||
UserCommentListGetForm,
|
||||
CourseDiscussionRolesForm,
|
||||
CourseDiscussionSettingsForm,
|
||||
ThreadListGetForm,
|
||||
UserCommentListGetForm
|
||||
)
|
||||
from ..rest_api.permissions import IsStaffOrCourseTeamOrEnrolled
|
||||
from ..rest_api.serializers import (
|
||||
CourseMetadataSerailizer,
|
||||
DiscussionRolesListSerializer,
|
||||
DiscussionRolesSerializer,
|
||||
)
|
||||
from ..rest_api.serializers import CourseMetadataSerailizer, DiscussionRolesListSerializer, DiscussionRolesSerializer
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ import logging
|
||||
from django.conf import settings
|
||||
from django.dispatch import receiver
|
||||
from opaque_keys.edx.locator import LibraryLocator
|
||||
from xmodule.modulestore.django import SignalHandler
|
||||
|
||||
from lms.djangoapps.discussion import tasks
|
||||
from openedx.core.djangoapps.django_comment_common import signals
|
||||
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
|
||||
from openedx.core.djangoapps.theming.helpers import get_current_site
|
||||
from xmodule.modulestore.django import SignalHandler # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ from openedx.core.djangolib.markup import HTML
|
||||
data-sort-preference="${sort_preference}"
|
||||
data-flag-moderator="${json.dumps(flag_moderator)}"
|
||||
data-user-group-id="${user_group_id}">
|
||||
<%include file="_switch_experience_fragment.html" />
|
||||
<header class="page-header has-secondary">
|
||||
## Breadcrumb navigation
|
||||
<div class="page-header-main">
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
## mako
|
||||
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
|
||||
<%page expression_filter="h"/>
|
||||
|
||||
<%!
|
||||
import json
|
||||
from django.utils.translation import ugettext as _
|
||||
%>
|
||||
|
||||
<section class="discussion discussion-board page-content-container" id="discussion-container"
|
||||
data-course-id="${course_key}" >
|
||||
<%include file="_switch_experience_fragment.html" />
|
||||
<iframe id='discussions-mfe-tab-embed' src='${discussions_mfe_url}'></iframe>
|
||||
</section>
|
||||
@@ -2,14 +2,18 @@
|
||||
Tests the forum notification signals.
|
||||
"""
|
||||
from unittest import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from edx_django_utils.cache import RequestCache
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import (
|
||||
CourseFactory,
|
||||
ItemFactory
|
||||
)
|
||||
|
||||
from lms.djangoapps.discussion.signals.handlers import ENABLE_FORUM_NOTIFICATIONS_FOR_SITE_KEY
|
||||
from openedx.core.djangoapps.django_comment_common import models, signals
|
||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory, SiteFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
class SendMessageHandlerTestCase(TestCase): # lint-amnesty, pylint: disable=missing-class-docstring
|
||||
|
||||
@@ -14,6 +14,7 @@ from edx_ace.channel import ChannelType, get_channel_for_message
|
||||
from edx_ace.recipient import Recipient
|
||||
from edx_ace.renderers import EmailRenderer
|
||||
from edx_ace.utils import date
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
|
||||
import openedx.core.djangoapps.django_comment_common.comment_client as cc
|
||||
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||
@@ -25,7 +26,6 @@ from openedx.core.djangoapps.django_comment_common.models import ForumsConfig
|
||||
from openedx.core.djangoapps.django_comment_common.signals import comment_created
|
||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory
|
||||
from openedx.core.lib.celery.task_utils import emulate_http_request
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
NOW = datetime.utcnow()
|
||||
ONE_HOUR_AGO = NOW - timedelta(hours=1)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
"""
|
||||
Tests the forum notification views.
|
||||
"""
|
||||
|
||||
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
@@ -16,11 +15,24 @@ from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils import translation
|
||||
from edx_django_utils.cache import RequestCache
|
||||
from edx_toggles.toggles.testutils import override_waffle_flag
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.tests.django_utils import (
|
||||
TEST_DATA_MONGO_MODULESTORE,
|
||||
ModuleStoreTestCase,
|
||||
SharedModuleStoreTestCase
|
||||
)
|
||||
from xmodule.modulestore.tests.factories import (
|
||||
CourseFactory,
|
||||
ItemFactory,
|
||||
check_mongo_calls
|
||||
)
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
|
||||
from common.djangoapps.student.roles import CourseStaffRole, UserBasedRole
|
||||
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||
from common.djangoapps.student.tests.factories import AdminFactory, CourseEnrollmentFactory, UserFactory
|
||||
from common.djangoapps.util.testing import EventTestMixin, UrlResetMixin
|
||||
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect
|
||||
from lms.djangoapps.discussion import views
|
||||
@@ -39,6 +51,7 @@ from lms.djangoapps.discussion.django_comment_client.tests.utils import (
|
||||
topic_name_to_id
|
||||
)
|
||||
from lms.djangoapps.discussion.django_comment_client.utils import strip_none
|
||||
from lms.djangoapps.discussion.toggles import ENABLE_DISCUSSIONS_MFE
|
||||
from lms.djangoapps.discussion.views import _get_discussion_default_topic_id, course_discussions_settings_handler
|
||||
from lms.djangoapps.teams.tests.factories import CourseTeamFactory, CourseTeamMembershipFactory
|
||||
from openedx.core.djangoapps.course_groups.models import CourseUserGroup
|
||||
@@ -56,14 +69,6 @@ from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES
|
||||
from openedx.core.lib.teams_config import TeamsConfig
|
||||
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseTestConsentRequired
|
||||
from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.django_utils import ( # lint-amnesty, pylint: disable=wrong-import-order
|
||||
TEST_DATA_MONGO_MODULESTORE,
|
||||
ModuleStoreTestCase,
|
||||
SharedModuleStoreTestCase
|
||||
)
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -2225,3 +2230,71 @@ class ThreadViewedEventTestCase(EventTestMixin, ForumsEnableMixin, UrlResetMixin
|
||||
_, event = self.get_latest_call_args()
|
||||
event_items = list(event.items())
|
||||
assert ((kv_pair in event_items) for kv_pair in expected_event_items)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@patch(
|
||||
'openedx.core.djangoapps.django_comment_common.comment_client.utils.perform_request',
|
||||
Mock(
|
||||
return_value={
|
||||
"default_sort_key": "date",
|
||||
"upvoted_ids": [],
|
||||
"downvoted_ids": [],
|
||||
"subscribed_thread_ids": [],
|
||||
}
|
||||
)
|
||||
)
|
||||
class ForumMFETestCase(ForumsEnableMixin, SharedModuleStoreTestCase):
|
||||
"""
|
||||
Tests that the MFE upgrade banner and MFE is shown in the correct situation with the correct UI
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.course = CourseFactory.create()
|
||||
self.user = UserFactory.create()
|
||||
self.staff_user = AdminFactory.create()
|
||||
|
||||
@ddt.data(*itertools.product(("http://test.url", None), (True, False), (True, False)))
|
||||
@ddt.unpack
|
||||
def test_staff_user(self, mfe_url, toggle_enabled, is_staff):
|
||||
"""
|
||||
Verify that the banner is shown with the correct links if the user is staff and the
|
||||
mfe url is configured.
|
||||
"""
|
||||
with override_settings(DISCUSSIONS_MICROFRONTEND_URL=mfe_url):
|
||||
with override_waffle_flag(ENABLE_DISCUSSIONS_MFE, toggle_enabled):
|
||||
username = self.staff_user.username if is_staff else self.user.username
|
||||
self.client.login(username=username, password='test')
|
||||
response = self.client.get(reverse("forum_form_discussion", args=[self.course.id]))
|
||||
content = response.content.decode('utf8')
|
||||
if mfe_url and is_staff:
|
||||
assert "made some changes to this experience!" in content
|
||||
if toggle_enabled:
|
||||
assert "legacy experience" in content
|
||||
assert "new experience" not in content
|
||||
else:
|
||||
assert "legacy experience" not in content
|
||||
assert "new experience" in content
|
||||
else:
|
||||
assert "made some changes to this experience!" not in content
|
||||
|
||||
@override_settings(DISCUSSIONS_MICROFRONTEND_URL="http://test.url")
|
||||
@ddt.data(*itertools.product((True, False), ("legacy", "new", None)))
|
||||
@ddt.unpack
|
||||
def test_correct_experience_is_shown(self, toggle_enabled, experience):
|
||||
"""
|
||||
Verify that the correct experience is shown based on the MFE toggle flag and the query param.
|
||||
"""
|
||||
with override_waffle_flag(ENABLE_DISCUSSIONS_MFE, toggle_enabled):
|
||||
self.client.login(username=self.staff_user.username, password='test')
|
||||
url = reverse("forum_form_discussion", args=[self.course.id])
|
||||
experience_in_url = ""
|
||||
if experience is not None:
|
||||
experience_in_url = f"discussions_experience={experience}"
|
||||
response = self.client.get(f"{url}?{experience_in_url}")
|
||||
content = response.content.decode('utf8')
|
||||
if (toggle_enabled and experience != "legacy") or experience == "new":
|
||||
assert "discussions-mfe-tab-embed" in content
|
||||
else:
|
||||
assert "discussions-mfe-tab-embed" not in content
|
||||
|
||||
@@ -2,27 +2,29 @@
|
||||
Views handling read (GET) requests for the Discussion tab and inline discussions.
|
||||
"""
|
||||
|
||||
|
||||
import logging
|
||||
from functools import wraps
|
||||
from typing import Dict, Optional
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
from django.http import Http404, HttpResponseForbidden, HttpResponseServerError
|
||||
from django.shortcuts import render
|
||||
from django.template.context_processors import csrf
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import get_language_bidi, gettext_lazy as _
|
||||
from django.utils.translation import get_language_bidi
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.decorators.cache import cache_control
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.views.decorators.http import require_GET, require_http_methods
|
||||
from edx_django_utils.monitoring import function_trace
|
||||
from functools import wraps # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from rest_framework import status
|
||||
from web_fragments.fragment import Fragment
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
import lms.djangoapps.discussion.django_comment_client.utils as utils
|
||||
import openedx.core.djangoapps.django_comment_common.comment_client as cc
|
||||
@@ -58,13 +60,11 @@ 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 # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
User = get_user_model()
|
||||
log = logging.getLogger("edx.discussions")
|
||||
|
||||
|
||||
THREADS_PER_PAGE = 20
|
||||
INLINE_THREADS_PER_PAGE = 20
|
||||
PAGES_NEARBY_DELTA = 2
|
||||
@@ -694,6 +694,38 @@ def followed_threads(request, course_key, user_id):
|
||||
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from
|
||||
|
||||
|
||||
def _discussions_mfe_context(query_params: Dict, course_key: CourseKey, user: User) -> Optional[Dict]:
|
||||
"""
|
||||
Returns the context for rendering the MFE banner and MFE.
|
||||
|
||||
Args:
|
||||
query_params (Dict): request query parameters
|
||||
course_key (CourseKey): course for which to get URL
|
||||
|
||||
Returns:
|
||||
A URL for the MFE experience if active for the current request or None
|
||||
"""
|
||||
experience_param = query_params.get("discussions_experience", "").lower()
|
||||
mfe_url = get_discussions_mfe_url(course_key)
|
||||
if not mfe_url:
|
||||
return {"show_banner": False, "show_mfe": False}
|
||||
show_banner = bool(has_access(user, 'staff', course_key))
|
||||
forum_url = reverse("forum_form_discussion", args=[course_key])
|
||||
show_mfe = False
|
||||
# Show the MFE if the new experience is requested,
|
||||
# or if the legacy experience is not requested and the MFE is enabled
|
||||
if experience_param == "new" or (experience_param != "legacy" and ENABLE_DISCUSSIONS_MFE.is_enabled(course_key)):
|
||||
show_mfe = True
|
||||
return {
|
||||
"show_mfe": show_mfe,
|
||||
"legacy_url": f"{forum_url}?discussions_experience=legacy",
|
||||
"mfe_url": f"{forum_url}?discussions_experience=new",
|
||||
"course_key": course_key,
|
||||
"show_banner": show_banner,
|
||||
"discussions_mfe_url": mfe_url,
|
||||
}
|
||||
|
||||
|
||||
class DiscussionBoardFragmentView(EdxFragmentView):
|
||||
"""
|
||||
Component implementation of the discussion board.
|
||||
@@ -721,13 +753,9 @@ class DiscussionBoardFragmentView(EdxFragmentView):
|
||||
Fragment: The fragment representing the discussion board
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
mfe_url = get_discussions_mfe_url(course_key)
|
||||
if ENABLE_DISCUSSIONS_MFE.is_enabled(course_key) and mfe_url:
|
||||
fragment = Fragment(
|
||||
HTML(
|
||||
"<iframe id='discussions-mfe-tab-embed' src='{src}'></iframe>"
|
||||
).format(src=mfe_url)
|
||||
)
|
||||
mfe_context = _discussions_mfe_context(request.GET, course_key, request.user)
|
||||
if mfe_context["show_mfe"]:
|
||||
fragment = Fragment(render_to_string('discussion/discussion_mfe_embed.html', mfe_context))
|
||||
fragment.add_css(
|
||||
"""
|
||||
#discussions-mfe-tab-embed {
|
||||
@@ -762,6 +790,7 @@ class DiscussionBoardFragmentView(EdxFragmentView):
|
||||
context.update({
|
||||
'course_expiration_fragment': course_expiration_fragment,
|
||||
})
|
||||
context.update(mfe_context)
|
||||
if profile_page_context:
|
||||
# EDUCATOR-2119: styles are hard to reconcile if the profile page isn't also a fragment
|
||||
html = render_to_string('discussion/discussion_profile_page.html', profile_page_context)
|
||||
|
||||
29
lms/templates/discussion/_switch_experience_fragment.html
Normal file
29
lms/templates/discussion/_switch_experience_fragment.html
Normal file
@@ -0,0 +1,29 @@
|
||||
## mako
|
||||
|
||||
<%page expression_filter="h"/>
|
||||
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
%>
|
||||
|
||||
% if show_banner:
|
||||
<div class="upgrade-banner d-flex bg-primary text-white align-items-center px-4 py-3">
|
||||
<div class="d-flex w-100">
|
||||
${_("We've made some changes to this experience! (Preview for educators only)")}
|
||||
</div>
|
||||
|
||||
% if show_mfe:
|
||||
<a class="btn btn-outline-light mr-2" href="${legacy_url}">
|
||||
${_("View legacy experience")}
|
||||
</a>
|
||||
% else:
|
||||
<a class="btn btn-outline-light mr-2" href="${mfe_url}">
|
||||
${_("View the new experience")}
|
||||
</a>
|
||||
% endif
|
||||
|
||||
<a class="btn btn-outline-light">
|
||||
${_("Share feedback")}
|
||||
</a>
|
||||
</div>
|
||||
% endif
|
||||
@@ -1,36 +1,56 @@
|
||||
"""
|
||||
Helps for building discussions URLs
|
||||
"""
|
||||
from typing import Optional
|
||||
|
||||
from django.conf import settings
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
|
||||
def get_discussions_mfe_url(course_key: CourseKey) -> str:
|
||||
def _get_url_with_view_query_params(path: str, view: Optional[str] = None) -> str:
|
||||
"""
|
||||
Helper function to build url if a url is configured
|
||||
|
||||
Args:
|
||||
path (str): The path in the discussions MFE
|
||||
view (str): which view to generate url for
|
||||
|
||||
Returns:
|
||||
(str) URL link for MFE
|
||||
|
||||
"""
|
||||
if settings.DISCUSSIONS_MICROFRONTEND_URL is None:
|
||||
return ''
|
||||
url = f"{settings.DISCUSSIONS_MICROFRONTEND_URL}/{path}"
|
||||
if view == "in_context":
|
||||
url = f"{url}?inContext"
|
||||
return url
|
||||
|
||||
|
||||
def get_discussions_mfe_url(course_key: CourseKey, view: Optional[str] = None) -> str:
|
||||
"""
|
||||
Returns the url for discussions for the specified course in the discussions MFE.
|
||||
|
||||
Args:
|
||||
course_key (CourseKey): course key of course for which to get url
|
||||
view (str): which view to generate url for
|
||||
|
||||
Returns:
|
||||
(str) URL link for MFE. Empty if the base url isn't configured
|
||||
"""
|
||||
if settings.DISCUSSIONS_MICROFRONTEND_URL is not None:
|
||||
return f"{settings.DISCUSSIONS_MICROFRONTEND_URL}/{course_key}/"
|
||||
return ''
|
||||
return _get_url_with_view_query_params(f"{course_key}/", view)
|
||||
|
||||
|
||||
def get_discussions_mfe_topic_url(course_key: CourseKey, topic_id: str) -> str:
|
||||
def get_discussions_mfe_topic_url(course_key: CourseKey, topic_id: str, view: Optional[str] = None) -> str:
|
||||
"""
|
||||
Returns the url for discussions for the specified course and topic in the discussions MFE.
|
||||
|
||||
Args:
|
||||
course_key (CourseKey): course key of course for which to get url
|
||||
topic_id (str): topic id for which to generate URL
|
||||
topic_id (str): topic id for topic to get url for
|
||||
view (str): which view to generate url for
|
||||
|
||||
Returns:
|
||||
(str) URL link for MFE. Empty if the base url isn't configured
|
||||
"""
|
||||
if settings.DISCUSSIONS_MICROFRONTEND_URL is not None:
|
||||
return f"{get_discussions_mfe_url(course_key)}topics/{topic_id}"
|
||||
return ''
|
||||
return _get_url_with_view_query_params(f"{course_key}/topics/{topic_id}", view)
|
||||
|
||||
@@ -11,16 +11,15 @@ 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 xblock.fields import UNIQUE_ID, Scope, String
|
||||
from xblockutils.resources import ResourceLoader
|
||||
from xblockutils.studio_editable import StudioEditableXBlockMixin
|
||||
from xmodule.xml_module import XmlParserMixin
|
||||
|
||||
from openedx.core.djangoapps.discussions.url_helpers import get_discussions_mfe_topic_url
|
||||
from lms.djangoapps.discussion.toggles import ENABLE_DISCUSSIONS_MFE
|
||||
from openedx.core.djangoapps.discussions.url_helpers import get_discussions_mfe_topic_url
|
||||
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 # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
loader = ResourceLoader(__name__) # pylint: disable=invalid-name
|
||||
|
||||
Reference in New Issue
Block a user