diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3f9abcc671fb..a05b78e8837a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -17,6 +17,7 @@ lms/djangoapps/instructor_task/ lms/djangoapps/mobile_api/ openedx/core/djangoapps/credentials @openedx/2U-aperture openedx/core/djangoapps/credit @openedx/2U-aperture +openedx/core/djangoapps/enrollments/ @openedx/2U-aperture openedx/core/djangoapps/heartbeat/ openedx/core/djangoapps/oauth_dispatch openedx/core/djangoapps/user_api/ @openedx/2U-aperture @@ -37,8 +38,9 @@ lms/djangoapps/certificates/ @openedx/2U- # Discovery common/djangoapps/course_modes/ common/djangoapps/enrollment/ +lms/djangoapps/branding/ @openedx/2U-aperture lms/djangoapps/commerce/ -lms/djangoapps/experiments/ +lms/djangoapps/experiments/ @openedx/2U-aperture lms/djangoapps/learner_dashboard/ @openedx/2U-aperture lms/djangoapps/learner_home/ @openedx/2U-aperture openedx/features/content_type_gating/ diff --git a/lms/djangoapps/bulk_email/tasks.py b/lms/djangoapps/bulk_email/tasks.py index 2b96af786a97..184dfd0e6869 100644 --- a/lms/djangoapps/bulk_email/tasks.py +++ b/lms/djangoapps/bulk_email/tasks.py @@ -474,6 +474,7 @@ def _send_course_email(entry_id, email_id, to_list, global_email_context, subtas 'course_id': str(course_email.course_id), 'to_list': [user_obj.get('email', '') for user_obj in to_list], 'total_recipients': total_recipients, + 'ace_enabled_for_bulk_email': is_bulk_email_edx_ace_enabled(), } ) # Exclude optouts (if not a retry): diff --git a/lms/djangoapps/discussion/rest_api/discussions_notifications.py b/lms/djangoapps/discussion/rest_api/discussions_notifications.py index 25abcf80d486..f65faf7f2a67 100644 --- a/lms/djangoapps/discussion/rest_api/discussions_notifications.py +++ b/lms/djangoapps/discussion/rest_api/discussions_notifications.py @@ -399,4 +399,18 @@ def clean_thread_html_body(html_body): for match in html_body.find_all(tag): match.unwrap() + # Replace tags that are not allowed in email + tags_to_update = [ + {"source": "button", "target": "span"}, + {"source": "h1", "target": "h4"}, + {"source": "h2", "target": "h4"}, + {"source": "h3", "target": "h4"}, + ] + for tag_dict in tags_to_update: + for source_tag in html_body.find_all(tag_dict['source']): + target_tag = html_body.new_tag(tag_dict['target'], **source_tag.attrs) + if source_tag.string: + target_tag.string = source_tag.string + source_tag.replace_with(target_tag) + return str(html_body) diff --git a/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py b/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py index f1a71fd1239e..d92e1000feb5 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_discussions_notifications.py @@ -168,3 +168,29 @@ def test_only_script_tag(self): result = clean_thread_html_body(html_body) self.assertEqual(result.strip(), expected_output) + + def test_button_tag_replace(self): + """ + Tests that the clean_thread_html_body function replaces the button tag with span tag + """ + # Tests for button replacement tag with text + html_body = '' + expected_output = 'Button' + result = clean_thread_html_body(html_body) + self.assertEqual(result, expected_output) + + # Tests button tag replacement without text + html_body = '' + expected_output = '' + result = clean_thread_html_body(html_body) + self.assertEqual(result, expected_output) + + def test_heading_tag_replace(self): + """ + Tests that the clean_thread_html_body function replaces the h1, h2 and h3 tags with h4 tag + """ + for tag in ['h1', 'h2', 'h3']: + html_body = f'<{tag}>Heading' + expected_output = '

Heading

' + result = clean_thread_html_body(html_body) + self.assertEqual(result, expected_output) diff --git a/openedx/core/djangoapps/content/search/api.py b/openedx/core/djangoapps/content/search/api.py index 3e0fadd717a9..a87510847068 100644 --- a/openedx/core/djangoapps/content/search/api.py +++ b/openedx/core/djangoapps/content/search/api.py @@ -316,6 +316,8 @@ def rebuild_index(status_cb: Callable[[str], None] | None = None) -> None: client.index(temp_index_name).update_distinct_attribute(Fields.usage_key) # Mark which attributes can be used for filtering/faceted search: client.index(temp_index_name).update_filterable_attributes([ + # Get specific block/collection using combination of block_id and context_key + Fields.block_id, Fields.block_type, Fields.context_key, Fields.org, diff --git a/openedx/core/djangoapps/notifications/base_notification.py b/openedx/core/djangoapps/notifications/base_notification.py index 02b49df89444..b57d88cea616 100644 --- a/openedx/core/djangoapps/notifications/base_notification.py +++ b/openedx/core/djangoapps/notifications/base_notification.py @@ -212,8 +212,8 @@ 'name': 'ora_grade_assigned', 'is_core': False, 'info': '', - 'web': False, - 'email': False, + 'web': True, + 'email': True, 'push': False, 'email_cadence': EmailCadence.DAILY, 'non_editable': [], diff --git a/openedx/core/djangoapps/notifications/tests/test_views.py b/openedx/core/djangoapps/notifications/tests/test_views.py index 27b369d925af..b7bd0414a27f 100644 --- a/openedx/core/djangoapps/notifications/tests/test_views.py +++ b/openedx/core/djangoapps/notifications/tests/test_views.py @@ -315,8 +315,8 @@ def _expected_api_response(self, course=None): 'info': 'Notifications for submission grading.' }, 'ora_grade_assigned': { - 'web': False, - 'email': False, + 'web': True, + 'email': True, 'push': False, 'email_cadence': 'Daily', 'info': ''