Merge branch 'master' into maq/#33589

This commit is contained in:
AbdullahQureshee
2024-08-22 16:32:30 +05:00
committed by GitHub
77 changed files with 3030 additions and 1987 deletions

View File

@@ -53,16 +53,3 @@ REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND = WaffleFlag(
# .. toggle_warning: Flag course_experience.relative_dates should also be active for relative dates functionalities to work.
# .. toggle_tickets: https://openedx.atlassian.net/browse/AA-844
CUSTOM_RELATIVE_DATES = CourseWaffleFlag(f'{WAFFLE_NAMESPACE}.custom_relative_dates', __name__)
# .. toggle_name: studio.enable_course_update_notifications
# .. toggle_implementation: CourseWaffleFlag
# .. toggle_default: False
# .. toggle_description: Waffle flag to enable course update notifications.
# .. toggle_use_cases: temporary, open_edx
# .. toggle_creation_date: 14-Feb-2024
# .. toggle_target_removal_date: 14-Mar-2024
ENABLE_COURSE_UPDATE_NOTIFICATIONS = CourseWaffleFlag(
f'{WAFFLE_NAMESPACE}.enable_course_update_notifications',
__name__
)

View File

@@ -19,7 +19,6 @@ import re
from django.http import HttpResponseBadRequest
from django.utils.translation import gettext as _
from cms.djangoapps.contentstore.config.waffle import ENABLE_COURSE_UPDATE_NOTIFICATIONS
from cms.djangoapps.contentstore.utils import track_course_update_event, send_course_update_notification
from openedx.core.lib.xblock_utils import get_course_update_items
from xmodule.html_block import CourseInfoBlock # lint-amnesty, pylint: disable=wrong-import-order
@@ -93,10 +92,9 @@ def update_course_updates(location, update, passed_id=None, user=None, request_m
track_course_update_event(location.course_key, user, course_update_dict)
# send course update notification
if ENABLE_COURSE_UPDATE_NOTIFICATIONS.is_enabled(location.course_key):
send_course_update_notification(
location.course_key, course_update_dict["content"], user,
)
send_course_update_notification(
location.course_key, course_update_dict["content"], user,
)
# remove status key
if "status" in course_update_dict:

View File

@@ -2255,7 +2255,7 @@ def send_course_update_notification(course_key, content, user):
"course_update_content": text_content if len(text_content.strip()) < 10 else "Click here to view",
**extra_context,
},
notification_type="course_update",
notification_type="course_updates",
content_url=f"{settings.LMS_ROOT_URL}/courses/{str(course_key)}/course/updates",
app_name="updates",
audience_filters={},

View File

@@ -58,6 +58,7 @@ from common.djangoapps.util.json_request import JsonResponse, JsonResponseBadReq
from common.djangoapps.util.string_utils import _has_non_ascii_characters
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.credit.tasks import update_credit_course_requirements
from openedx.core.djangoapps.discussions.tasks import update_discussions_settings_from_course
from openedx.core.djangoapps.models.course_details import CourseDetails
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangolib.js_utils import dump_js_escaped_json
@@ -302,6 +303,10 @@ def course_handler(request, course_key_string=None):
else:
return HttpResponseBadRequest()
elif request.method == 'GET': # assume html
# Update course discussion settings, sometimes the course discussion settings are not updated
# when the course is created, so we need to update them here.
course_key = CourseKey.from_string(course_key_string)
update_discussions_settings_from_course(course_key)
if course_key_string is None:
return redirect(reverse('home'))
else:

View File

@@ -717,8 +717,8 @@ class TestCourseOutline(CourseTestCase):
"""
Test to check number of queries made to mysql and mongo
"""
with self.assertNumQueries(29, table_ignorelist=WAFFLE_TABLES):
with check_mongo_calls(3):
with self.assertNumQueries(32, table_ignorelist=WAFFLE_TABLES):
with check_mongo_calls(5):
self.client.get_html(reverse_course_url('course_handler', self.course.id))

View File

@@ -15,6 +15,8 @@
// +Libs and Resets - *do not edit*
// ====================
@import '_builtin-block-variables';
@import 'bourbon/bourbon'; // lib - bourbon
@import 'vendor/bi-app/bi-app-ltr'; // set the layout for left to right languages
@import 'build-v1'; // shared app style assets/rendering

View File

@@ -0,0 +1,73 @@
/*
* In pursuit of decoupling the built-in XBlocks from edx-platform's Sass build
* and ensuring comprehensive theming support in the extracted XBlocks,
* we need to expose Sass variables as CSS variables.
*
* Ticket/Issue: https://github.com/openedx/edx-platform/issues/35173
*/
@import 'bourbon/bourbon';
@import 'lms/theme/variables';
@import 'lms/theme/variables-v1';
@import 'cms/static/sass/partials/cms/theme/_variables';
@import 'cms/static/sass/partials/cms/theme/_variables-v1';
@import 'bootstrap/scss/variables';
@import 'vendor/bi-app/bi-app-ltr';
@import 'edx-pattern-library-shims/base/_variables.scss';
:root {
--action-primary-active-bg: $action-primary-active-bg;
--all-text-inputs: $all-text-inputs;
--base-font-size: $base-font-size;
--base-line-height: $base-line-height;
--baseline: $baseline;
--black: $black;
--black-t2: $black-t2;
--blue: $blue;
--blue-d1: $blue-d1;
--blue-d2: $blue-d2;
--blue-d4: $blue-d4;
--body-color: $body-color;
--border-color: $border-color;
--bp-screen-lg: $bp-screen-lg;
--btn-brand-focus-background: $btn-brand-focus-background;
--correct: $correct;
--danger: $danger;
--darkGrey: $darkGrey;
--error-color: $error-color;
--font-bold: $font-bold;
--font-family-sans-serif: $font-family-sans-serif;
--general-color-accent: $general-color-accent;
--gray: $gray;
--gray-300: $gray-300;
--gray-d1: $gray-d1;
--gray-l2: $gray-l2;
--gray-l3: $gray-l3;
--gray-l4: $gray-l4;
--gray-l6: $gray-l6;
--incorrect: $incorrect;
--lightGrey: $lightGrey;
--lighter-base-font-color: $lighter-base-font-color;
--link-color: $link-color;
--medium-font-size: $medium-font-size;
--partially-correct: $partially-correct;
--primary: $primary;
--shadow: $shadow;
--shadow-l1: $shadow-l1;
--sidebar-color: $sidebar-color;
--small-font-size: $small-font-size;
--static-path: $static-path;
--submitted: $submitted;
--success: $success;
--tmg-f2: $tmg-f2;
--tmg-s2: $tmg-s2;
--transparent: $transparent;
--uxpl-gray-background: $uxpl-gray-background;
--uxpl-gray-base: $uxpl-gray-base;
--uxpl-gray-dark: $uxpl-gray-dark;
--very-light-text: $very-light-text;
--warning: $warning;
--warning-color: $warning-color;
--warning-color-accent: $warning-color-accent;
--white: $white;
--yellow: $yellow;
}

View File

@@ -7,3 +7,7 @@ class BulkEmailConfig(AppConfig):
Application Configuration for bulk_email.
"""
name = 'lms.djangoapps.bulk_email'
def ready(self):
import lms.djangoapps.bulk_email.signals # lint-amnesty, pylint: disable=unused-import
from edx_ace.signals import ACE_MESSAGE_SENT # lint-amnesty, pylint: disable=unused-import

View File

@@ -1,12 +1,13 @@
"""
Signal handlers for the bulk_email app
"""
from django.contrib.auth import get_user_model
from django.dispatch import receiver
from eventtracking import tracker
from common.djangoapps.student.models import CourseEnrollment
from openedx.core.djangoapps.user_api.accounts.signals import USER_RETIRE_MAILINGS
from edx_ace.signals import ACE_MESSAGE_SENT
from .models import Optout
@@ -24,3 +25,28 @@ def force_optout_all(sender, **kwargs): # lint-amnesty, pylint: disable=unused-
for enrollment in CourseEnrollment.objects.filter(user=user):
Optout.objects.get_or_create(user=user, course_id=enrollment.course.id)
@receiver(ACE_MESSAGE_SENT)
def ace_email_sent_handler(sender, **kwargs):
"""
When an email is sent using ACE, this method will create an event to detect ace email success status
"""
# Fetch the message object from kwargs, defaulting to None if not present
message = kwargs.get('message', None)
user_model = get_user_model()
try:
user_id = user_model.objects.get(email=message.recipient.email_address).id
except user_model.DoesNotExist:
user_id = None
course_email = message.context.get('course_email', None)
course_id = course_email.course_id if course_email else None
tracker.emit(
'edx.bulk_email.sent',
{
'message_type': message.name,
'course_id': course_id,
'user_id': user_id,
}
)

View File

@@ -26,6 +26,7 @@ from django.utils import timezone
from django.utils.translation import gettext as _
from django.utils.translation import override as override_language
from edx_django_utils.monitoring import set_code_owner_attribute
from eventtracking import tracker
from markupsafe import escape
from common.djangoapps.util.date_utils import get_default_time_display
@@ -467,7 +468,14 @@ def _send_course_email(entry_id, email_id, to_list, global_email_context, subtas
"send."
)
raise exc
tracker.emit(
'edx.bulk_email.created',
{
'course_id': str(course_email.course_id),
'to_list': to_list,
'total_recipients': total_recipients,
}
)
# Exclude optouts (if not a retry):
# Note that we don't have to do the optout logic at all if this is a retry,
# because we have presumably already performed the optout logic on the first

View File

@@ -7,6 +7,7 @@ import logging
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
from django.http import Http404
from eventtracking import tracker
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
@@ -60,4 +61,12 @@ def opt_out_email_updates(request, token, course_id):
course_id,
)
tracker.emit(
'edx.bulk_email.opt_out',
{
'course_id': course_id,
'user_id': user.id,
}
)
return render_to_response('bulk_email/unsubscribe_success.html', context)

View File

@@ -1458,6 +1458,8 @@ class CreateSubCommentUnicodeTestCase(
@disable_signal(views, 'comment_created')
@disable_signal(views, 'comment_voted')
@disable_signal(views, 'comment_deleted')
@disable_signal(views, 'comment_flagged')
@disable_signal(views, 'thread_flagged')
class TeamsPermissionsTestCase(ForumsEnableMixin, UrlResetMixin, SharedModuleStoreTestCase, MockRequestSetupMixin):
# Most of the test points use the same ddt data.
# args: user, commentable_id, status_code

View File

@@ -118,9 +118,10 @@ class DiscussionNotificationSender:
self.parent_response and
self.creator.id != int(self.thread.user_id)
):
author_name = f"{self.parent_response.username}'s"
# use your if author of response is same as author of post.
# use 'their' if comment author is also response author.
author_name = (
author_pronoun = (
# Translators: Replier commented on "your" response to your post
_("your")
if self._response_and_thread_has_same_creator()
@@ -129,10 +130,12 @@ class DiscussionNotificationSender:
_("their")
if self._response_and_comment_has_same_creator()
else f"{self.parent_response.username}'s"
)
)
context = {
"author_name": str(author_name),
"author_pronoun": str(author_pronoun),
}
self._send_notification([self.thread.user_id], "new_comment", extra_context=context)
@@ -189,10 +192,21 @@ class DiscussionNotificationSender:
if not self.parent_id:
self._send_notification(users, "response_on_followed_post")
else:
author_name = f"{self.parent_response.username}'s"
# use 'their' if comment author is also response author.
author_pronoun = (
# Translators: Replier commented on "their" response in a post you're following
_("their")
if self._response_and_comment_has_same_creator()
else f"{self.parent_response.username}'s"
)
self._send_notification(
users,
"comment_on_followed_post",
extra_context={"author_name": self.parent_response.username}
extra_context={
"author_name": str(author_name),
"author_pronoun": str(author_pronoun),
}
)
def _create_cohort_course_audience(self):
@@ -300,7 +314,7 @@ class DiscussionNotificationSender:
content_type = thread_types[self.thread.type][getattr(self.thread, 'depth', 0)]
context = {
'username': self.creator.username,
'username': self.thread.username,
'content_type': content_type,
'content': thread_body
}

View File

@@ -44,7 +44,7 @@ class TestDiscussionNotificationSender(unittest.TestCase):
self.assertEqual(notification_type, "content_reported")
self.assertEqual(context, {
'username': 'test_user',
'username': self.thread.username,
'content_type': expected_content_type,
'content': 'Thread body'
})

View File

@@ -338,6 +338,7 @@ class TestSendResponseNotifications(DiscussionAPIViewTestMixin, ModuleStoreTestC
'replier_name': self.user_3.username,
'post_title': self.thread.title,
'author_name': 'dummy\'s',
'author_pronoun': 'dummy\'s',
'course_name': self.course.display_name,
'sender_id': self.user_3.id
}
@@ -399,7 +400,8 @@ class TestSendResponseNotifications(DiscussionAPIViewTestMixin, ModuleStoreTestC
expected_context = {
'replier_name': self.user_3.username,
'post_title': self.thread.title,
'author_name': 'your',
'author_name': 'dummy\'s',
'author_pronoun': 'your',
'course_name': self.course.display_name,
'sender_id': self.user_3.id,
}
@@ -441,7 +443,8 @@ class TestSendResponseNotifications(DiscussionAPIViewTestMixin, ModuleStoreTestC
'sender_id': self.user_2.id,
}
if parent_id:
expected_context['author_name'] = 'dummy'
expected_context['author_name'] = 'dummy\'s'
expected_context['author_pronoun'] = 'dummy\'s'
self.assertDictEqual(args.context, expected_context)
self.assertEqual(
args.content_url,
@@ -531,7 +534,8 @@ class TestSendCommentNotification(DiscussionAPIViewTestMixin, ModuleStoreTestCas
send_response_notifications(thread.id, str(self.course.id), self.user_2.id, parent_id=response.id)
handler.assert_called_once()
context = handler.call_args[1]['notification_data'].context
self.assertEqual(context['author_name'], 'their')
self.assertEqual(context['author_name'], 'dummy\'s')
self.assertEqual(context['author_pronoun'], 'their')
@override_waffle_flag(ENABLE_NOTIFICATIONS, active=True)

View File

@@ -7,6 +7,7 @@ import logging
from datetime import timedelta
from django.utils import timezone
from django.conf import settings
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
@@ -22,7 +23,7 @@ def are_grades_frozen(course_key):
if ENFORCE_FREEZE_GRADE_AFTER_COURSE_END.is_enabled(course_key):
course = CourseOverview.get_from_id(course_key)
if course.end:
freeze_grade_date = course.end + timedelta(30)
freeze_grade_date = course.end + timedelta(settings.GRADEBOOK_FREEZE_DAYS)
now = timezone.now()
return now > freeze_grade_date
return False

View File

@@ -1,11 +1,13 @@
"""
Permissions for the instructor dashboard and associated actions
"""
from bridgekeeper import perms
from bridgekeeper.rules import is_staff
from opaque_keys.edx.keys import CourseKey
from rest_framework.permissions import BasePermission
from lms.djangoapps.courseware.rules import HasAccessRule, HasRolesRule
from openedx.core.lib.courses import get_course_by_id
ALLOW_STUDENT_TO_BYPASS_ENTRANCE_EXAM = 'instructor.allow_student_to_bypass_entrance_exam'
ASSIGN_TO_COHORTS = 'instructor.assign_to_cohorts'
@@ -72,3 +74,11 @@ perms[VIEW_DASHBOARD] = \
) | HasAccessRule('staff') | HasAccessRule('instructor')
perms[VIEW_ENROLLMENTS] = HasAccessRule('staff')
perms[VIEW_FORUM_MEMBERS] = HasAccessRule('staff')
class InstructorPermission(BasePermission):
"""Generic permissions"""
def has_permission(self, request, view):
course = get_course_by_id(CourseKey.from_string(view.kwargs.get('course_id')))
permission = getattr(view, 'permission_name', None)
return request.user.has_perm(permission, course)

View File

@@ -642,6 +642,25 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(SharedModuleStoreTestCas
last_name='Student'
)
def test_api_without_login(self):
"""
verify in case of no authentication it returns 401.
"""
self.client.logout()
uploaded_file = SimpleUploadedFile("temp.jpg", io.BytesIO(b"some initial binary data: \x00\x01").read())
response = self.client.post(self.url, {'students_list': uploaded_file})
assert response.status_code == 401
def test_api_without_permission(self):
"""
verify in case of no authentication it returns 403.
"""
# removed role from course for instructor
CourseInstructorRole(self.course.id).remove_users(self.instructor)
uploaded_file = SimpleUploadedFile("temp.jpg", io.BytesIO(b"some initial binary data: \x00\x01").read())
response = self.client.post(self.url, {'students_list': uploaded_file})
assert response.status_code == 403
@patch('lms.djangoapps.instructor.views.api.log.info')
@ddt.data(
b"test_student@example.com,test_student_1,tester1,USA", # Typical use case.
@@ -2945,7 +2964,37 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
response = self.client.post(url, data)
assert response.status_code == 200
res_json = json.loads(response.content.decode('utf-8'))
assert 'progress_url' in res_json
expected_data = {
'course_id': str(self.course.id),
'progress_url': f'/courses/{self.course.id}/progress/{self.students[0].id}/'
}
for key, value in expected_data.items():
self.assertIn(key, res_json)
self.assertEqual(res_json[key], value)
def test_get_student_progress_url_response_headers(self):
"""
Test that the progress_url endpoint returns the correct headers.
"""
url = reverse('get_student_progress_url', kwargs={'course_id': str(self.course.id)})
data = {'unique_student_identifier': self.students[0].email}
response = self.client.post(url, data)
assert response.status_code == 200
expected_headers = {
'Allow': 'POST, OPTIONS', # drf view brings this key.
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Content-Language': 'en',
'Content-Length': str(len(response.content.decode('utf-8'))),
'Content-Type': 'application/json',
'Vary': 'Cookie, Accept-Language, origin',
'X-Frame-Options': 'DENY'
}
for key, value in expected_headers.items():
self.assertIn(key, response.headers)
self.assertEqual(response.headers[key], value)
def test_get_student_progress_url_from_uname(self):
""" Test that progress_url is in the successful response. """
@@ -2955,6 +3004,14 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
assert response.status_code == 200
res_json = json.loads(response.content.decode('utf-8'))
assert 'progress_url' in res_json
expected_data = {
'course_id': str(self.course.id),
'progress_url': f'/courses/{self.course.id}/progress/{self.students[0].id}/'
}
for key, value in expected_data.items():
self.assertIn(key, res_json)
self.assertEqual(res_json[key], value)
def test_get_student_progress_url_noparams(self):
""" Test that the endpoint 404's without the required query params. """
@@ -2968,6 +3025,17 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
response = self.client.post(url)
assert response.status_code == 400
def test_get_student_progress_url_without_permissions(self):
""" Test that progress_url returns 403 without credentials. """
# removed both roles from courses for instructor
CourseDataResearcherRole(self.course.id).remove_users(self.instructor)
CourseInstructorRole(self.course.id).remove_users(self.instructor)
url = reverse('get_student_progress_url', kwargs={'course_id': str(self.course.id)})
data = {'unique_student_identifier': self.students[0].email}
response = self.client.post(url, data)
assert response.status_code == 403
class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
"""

View File

@@ -105,6 +105,7 @@ from lms.djangoapps.instructor_task import api as task_api
from lms.djangoapps.instructor_task.api_helper import AlreadyRunningError, QueueConnectionError
from lms.djangoapps.instructor_task.data import InstructorTaskTypes
from lms.djangoapps.instructor_task.models import ReportStore
from lms.djangoapps.instructor.views.serializer import RoleNameSerializer, UserSerializer, AccessSerializer
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort, is_course_cohorted
from openedx.core.djangoapps.course_groups.models import CourseUserGroup
@@ -122,6 +123,7 @@ from openedx.core.djangolib.markup import HTML, Text
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes
from openedx.core.lib.courses import get_course_by_id
from openedx.core.lib.api.serializers import CourseKeyField
from openedx.features.course_experience.url_helpers import get_learning_mfe_home_url
from .tools import (
dump_block_extensions,
@@ -281,299 +283,305 @@ def require_finance_admin(func):
return wrapped
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_course_permission(permissions.CAN_ENROLL)
def register_and_enroll_students(request, course_id): # pylint: disable=too-many-statements
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True), name='dispatch')
class RegisterAndEnrollStudents(APIView):
"""
Create new account and Enroll students in this course.
Passing a csv file that contains a list of students.
Order in csv should be the following email = 0; username = 1; name = 2; country = 3.
If there are more than 4 columns in the csv: cohort = 4, course mode = 5.
Requires staff access.
-If the email address and username already exists and the user is enrolled in the course,
do nothing (including no email gets sent out)
-If the email address already exists, but the username is different,
match on the email address only and continue to enroll the user in the course using the email address
as the matching criteria. Note the change of username as a warning message (but not a failure).
Send a standard enrollment email which is the same as the existing manual enrollment
-If the username already exists (but not the email), assume it is a different user and fail
to create the new account.
The failure will be messaged in a response in the browser.
"""
permission_classes = (IsAuthenticated, permissions.InstructorPermission)
permission_name = permissions.CAN_ENROLL
if not configuration_helpers.get_value(
'ALLOW_AUTOMATED_SIGNUPS',
settings.FEATURES.get('ALLOW_AUTOMATED_SIGNUPS', False),
):
return HttpResponseForbidden()
@method_decorator(ensure_csrf_cookie)
def post(self, request, course_id): # pylint: disable=too-many-statements
"""
Create new account and Enroll students in this course.
Passing a csv file that contains a list of students.
Order in csv should be the following email = 0; username = 1; name = 2; country = 3.
If there are more than 4 columns in the csv: cohort = 4, course mode = 5.
Requires staff access.
course_id = CourseKey.from_string(course_id)
warnings = []
row_errors = []
general_errors = []
-If the email address and username already exists and the user is enrolled in the course,
do nothing (including no email gets sent out)
# email-students is a checkbox input type; will be present in POST if checked, absent otherwise
notify_by_email = 'email-students' in request.POST
-If the email address already exists, but the username is different,
match on the email address only and continue to enroll the user in the course using the email address
as the matching criteria. Note the change of username as a warning message (but not a failure).
Send a standard enrollment email which is the same as the existing manual enrollment
# for white labels we use 'shopping cart' which uses CourseMode.HONOR as
# course mode for creating course enrollments.
if CourseMode.is_white_label(course_id):
default_course_mode = CourseMode.HONOR
else:
default_course_mode = None
-If the username already exists (but not the email), assume it is a different user and fail
to create the new account.
The failure will be messaged in a response in the browser.
"""
if not configuration_helpers.get_value(
'ALLOW_AUTOMATED_SIGNUPS',
settings.FEATURES.get('ALLOW_AUTOMATED_SIGNUPS', False),
):
return HttpResponseForbidden()
# Allow bulk enrollments in all non-expired course modes including "credit" (which is non-selectable)
valid_course_modes = set(map(lambda x: x.slug, CourseMode.modes_for_course(
course_id=course_id,
only_selectable=False,
include_expired=False,
)))
course_id = CourseKey.from_string(course_id)
warnings = []
row_errors = []
general_errors = []
if 'students_list' in request.FILES: # lint-amnesty, pylint: disable=too-many-nested-blocks
students = []
# email-students is a checkbox input type; will be present in POST if checked, absent otherwise
notify_by_email = 'email-students' in request.POST
try:
upload_file = request.FILES.get('students_list')
if upload_file.name.endswith('.csv'):
students = list(csv.reader(upload_file.read().decode('utf-8-sig').splitlines()))
course = get_course_by_id(course_id)
else:
general_errors.append({
'username': '', 'email': '',
'response': _(
'Make sure that the file you upload is in CSV format with no extraneous characters or rows.')
})
# for white labels we use 'shopping cart' which uses CourseMode.HONOR as
# course mode for creating course enrollments.
if CourseMode.is_white_label(course_id):
default_course_mode = CourseMode.HONOR
else:
default_course_mode = None
except Exception: # pylint: disable=broad-except
general_errors.append({
'username': '', 'email': '', 'response': _('Could not read uploaded file.')
})
finally:
upload_file.close()
# Allow bulk enrollments in all non-expired course modes including "credit" (which is non-selectable)
valid_course_modes = set(map(lambda x: x.slug, CourseMode.modes_for_course(
course_id=course_id,
only_selectable=False,
include_expired=False,
)))
generated_passwords = []
# To skip fetching cohorts from the DB while iterating on students,
# {<cohort name>: CourseUserGroup}
cohorts_cache = {}
already_warned_not_cohorted = False
extra_fields_is_enabled = configuration_helpers.get_value(
'ENABLE_AUTOMATED_SIGNUPS_EXTRA_FIELDS',
settings.FEATURES.get('ENABLE_AUTOMATED_SIGNUPS_EXTRA_FIELDS', False),
)
if 'students_list' in request.FILES: # lint-amnesty, pylint: disable=too-many-nested-blocks
students = []
# Iterate each student in the uploaded csv file.
for row_num, student in enumerate(students, 1):
# Verify that we have the expected number of columns in every row
# but allow for blank lines.
if not student:
continue
if extra_fields_is_enabled:
is_valid_csv = 4 <= len(student) <= 6
error = _('Data in row #{row_num} must have between four and six columns: '
'email, username, full name, country, cohort, and course mode. '
'The last two columns are optional.').format(row_num=row_num)
else:
is_valid_csv = len(student) == 4
error = _('Data in row #{row_num} must have exactly four columns: '
'email, username, full name, and country.').format(row_num=row_num)
if not is_valid_csv:
general_errors.append({
'username': '',
'email': '',
'response': error
})
continue
# Extract each column, handle optional columns if they exist.
email, username, name, country, *optional_cols = student
if optional_cols:
optional_cols.append(default_course_mode)
cohort_name, course_mode, *_tail = optional_cols
else:
cohort_name = None
course_mode = None
# Validate cohort name, and get the cohort object. Skip if course
# is not cohorted.
cohort = None
if cohort_name and not already_warned_not_cohorted:
if not is_course_cohorted(course_id):
row_errors.append({
'username': username,
'email': email,
'response': _('Course is not cohorted but cohort provided. '
'Ignoring cohort assignment for all users.')
})
already_warned_not_cohorted = True
elif cohort_name in cohorts_cache:
cohort = cohorts_cache[cohort_name]
try:
upload_file = request.FILES.get('students_list')
if upload_file.name.endswith('.csv'):
students = list(csv.reader(upload_file.read().decode('utf-8-sig').splitlines()))
course = get_course_by_id(course_id)
else:
# Don't attempt to create cohort or assign student if cohort
# does not exist.
try:
cohort = get_cohort_by_name(course_id, cohort_name)
except CourseUserGroup.DoesNotExist:
general_errors.append({
'username': '', 'email': '',
'response': _(
'Make sure that the file you upload is in CSV format with no '
'extraneous characters or rows.')
})
except Exception: # pylint: disable=broad-except
general_errors.append({
'username': '', 'email': '', 'response': _('Could not read uploaded file.')
})
finally:
upload_file.close()
generated_passwords = []
# To skip fetching cohorts from the DB while iterating on students,
# {<cohort name>: CourseUserGroup}
cohorts_cache = {}
already_warned_not_cohorted = False
extra_fields_is_enabled = configuration_helpers.get_value(
'ENABLE_AUTOMATED_SIGNUPS_EXTRA_FIELDS',
settings.FEATURES.get('ENABLE_AUTOMATED_SIGNUPS_EXTRA_FIELDS', False),
)
# Iterate each student in the uploaded csv file.
for row_num, student in enumerate(students, 1):
# Verify that we have the expected number of columns in every row
# but allow for blank lines.
if not student:
continue
if extra_fields_is_enabled:
is_valid_csv = 4 <= len(student) <= 6
error = _('Data in row #{row_num} must have between four and six columns: '
'email, username, full name, country, cohort, and course mode. '
'The last two columns are optional.').format(row_num=row_num)
else:
is_valid_csv = len(student) == 4
error = _('Data in row #{row_num} must have exactly four columns: '
'email, username, full name, and country.').format(row_num=row_num)
if not is_valid_csv:
general_errors.append({
'username': '',
'email': '',
'response': error
})
continue
# Extract each column, handle optional columns if they exist.
email, username, name, country, *optional_cols = student
if optional_cols:
optional_cols.append(default_course_mode)
cohort_name, course_mode, *_tail = optional_cols
else:
cohort_name = None
course_mode = None
# Validate cohort name, and get the cohort object. Skip if course
# is not cohorted.
cohort = None
if cohort_name and not already_warned_not_cohorted:
if not is_course_cohorted(course_id):
row_errors.append({
'username': username,
'email': email,
'response': _('Cohort name not found: {cohort}. '
'Ignoring cohort assignment for '
'all users.').format(cohort=cohort_name)
'response': _('Course is not cohorted but cohort provided. '
'Ignoring cohort assignment for all users.')
})
cohorts_cache[cohort_name] = cohort
# Validate course mode.
if not course_mode:
course_mode = default_course_mode
if (course_mode is not None
and course_mode not in valid_course_modes):
# If `default is None` and the user is already enrolled,
# `CourseEnrollment.change_mode()` will not update the mode,
# hence two error messages.
if default_course_mode is None:
err_msg = _(
'Invalid course mode: {mode}. Falling back to the '
'default mode, or keeping the current mode in case the '
'user is already enrolled.'
).format(mode=course_mode)
else:
err_msg = _(
'Invalid course mode: {mode}. Failling back to '
'{default_mode}, or resetting to {default_mode} in case '
'the user is already enrolled.'
).format(mode=course_mode, default_mode=default_course_mode)
row_errors.append({
'username': username,
'email': email,
'response': err_msg,
})
course_mode = default_course_mode
email_params = get_email_params(course, True, secure=request.is_secure())
try:
validate_email(email) # Raises ValidationError if invalid
except ValidationError:
row_errors.append({
'username': username,
'email': email,
'response': _('Invalid email {email_address}.').format(email_address=email)
})
else:
if User.objects.filter(email=email).exists():
# Email address already exists. assume it is the correct user
# and just register the user in the course and send an enrollment email.
user = User.objects.get(email=email)
# see if it is an exact match with email and username
# if it's not an exact match then just display a warning message, but continue onwards
if not User.objects.filter(email=email, username=username).exists():
warning_message = _(
'An account with email {email} exists but the provided username {username} '
'is different. Enrolling anyway with {email}.'
).format(email=email, username=username)
warnings.append({
'username': username, 'email': email, 'response': warning_message
})
log.warning('email %s already exist', email)
already_warned_not_cohorted = True
elif cohort_name in cohorts_cache:
cohort = cohorts_cache[cohort_name]
else:
log.info(
"user already exists with username '%s' and email '%s'",
username,
email
)
# enroll a user if it is not already enrolled.
if not is_user_enrolled_in_course(user, course_id):
# Enroll user to the course and add manual enrollment audit trail
create_manual_course_enrollment(
user=user,
course_id=course_id,
mode=course_mode,
enrolled_by=request.user,
reason='Enrolling via csv upload',
state_transition=UNENROLLED_TO_ENROLLED,
)
enroll_email(course_id=course_id,
student_email=email,
auto_enroll=True,
email_students=notify_by_email,
email_params=email_params)
else:
# update the course mode if already enrolled
existing_enrollment = CourseEnrollment.get_enrollment(user, course_id)
if existing_enrollment.mode != course_mode:
existing_enrollment.change_mode(mode=course_mode)
if cohort:
# Don't attempt to create cohort or assign student if cohort
# does not exist.
try:
add_user_to_cohort(cohort, user)
except ValueError:
# user already in this cohort; ignore
pass
elif is_email_retired(email):
# We are either attempting to enroll a retired user or create a new user with an email which is
# already associated with a retired account. Simply block these attempts.
row_errors.append({
'username': username,
'email': email,
'response': _('Invalid email {email_address}.').format(email_address=email),
})
log.warning('Email address %s is associated with a retired user, so course enrollment was ' + # lint-amnesty, pylint: disable=logging-not-lazy
'blocked.', email)
else:
# This email does not yet exist, so we need to create a new account
# If username already exists in the database, then create_and_enroll_user
# will raise an IntegrityError exception.
password = generate_unique_password(generated_passwords)
errors = create_and_enroll_user(
email,
username,
name,
country,
password,
course_id,
course_mode,
request.user,
email_params,
email_user=notify_by_email,
)
row_errors.extend(errors)
if cohort:
try:
add_user_to_cohort(cohort, email)
except ValueError:
# user already in this cohort; ignore
# NOTE: Checking this here may be unnecessary if we can prove that a new user will never be
# automatically assigned to a cohort from the above.
pass
except ValidationError:
cohort = get_cohort_by_name(course_id, cohort_name)
except CourseUserGroup.DoesNotExist:
row_errors.append({
'username': username,
'email': email,
'response': _('Invalid email {email_address}.').format(email_address=email),
'response': _('Cohort name not found: {cohort}. '
'Ignoring cohort assignment for '
'all users.').format(cohort=cohort_name)
})
cohorts_cache[cohort_name] = cohort
else:
general_errors.append({
'username': '', 'email': '', 'response': _('File is not attached.')
})
# Validate course mode.
if not course_mode:
course_mode = default_course_mode
results = {
'row_errors': row_errors,
'general_errors': general_errors,
'warnings': warnings
}
return JsonResponse(results)
if (course_mode is not None
and course_mode not in valid_course_modes):
# If `default is None` and the user is already enrolled,
# `CourseEnrollment.change_mode()` will not update the mode,
# hence two error messages.
if default_course_mode is None:
err_msg = _(
'Invalid course mode: {mode}. Falling back to the '
'default mode, or keeping the current mode in case the '
'user is already enrolled.'
).format(mode=course_mode)
else:
err_msg = _(
'Invalid course mode: {mode}. Failling back to '
'{default_mode}, or resetting to {default_mode} in case '
'the user is already enrolled.'
).format(mode=course_mode, default_mode=default_course_mode)
row_errors.append({
'username': username,
'email': email,
'response': err_msg,
})
course_mode = default_course_mode
email_params = get_email_params(course, True, secure=request.is_secure())
try:
validate_email(email) # Raises ValidationError if invalid
except ValidationError:
row_errors.append({
'username': username,
'email': email,
'response': _('Invalid email {email_address}.').format(email_address=email)
})
else:
if User.objects.filter(email=email).exists():
# Email address already exists. assume it is the correct user
# and just register the user in the course and send an enrollment email.
user = User.objects.get(email=email)
# see if it is an exact match with email and username
# if it's not an exact match then just display a warning message, but continue onwards
if not User.objects.filter(email=email, username=username).exists():
warning_message = _(
'An account with email {email} exists but the provided username {username} '
'is different. Enrolling anyway with {email}.'
).format(email=email, username=username)
warnings.append({
'username': username, 'email': email, 'response': warning_message
})
log.warning('email %s already exist', email)
else:
log.info(
"user already exists with username '%s' and email '%s'",
username,
email
)
# enroll a user if it is not already enrolled.
if not is_user_enrolled_in_course(user, course_id):
# Enroll user to the course and add manual enrollment audit trail
create_manual_course_enrollment(
user=user,
course_id=course_id,
mode=course_mode,
enrolled_by=request.user,
reason='Enrolling via csv upload',
state_transition=UNENROLLED_TO_ENROLLED,
)
enroll_email(course_id=course_id,
student_email=email,
auto_enroll=True,
email_students=notify_by_email,
email_params=email_params)
else:
# update the course mode if already enrolled
existing_enrollment = CourseEnrollment.get_enrollment(user, course_id)
if existing_enrollment.mode != course_mode:
existing_enrollment.change_mode(mode=course_mode)
if cohort:
try:
add_user_to_cohort(cohort, user)
except ValueError:
# user already in this cohort; ignore
pass
elif is_email_retired(email):
# We are either attempting to enroll a retired user or create a new user with an email which is
# already associated with a retired account. Simply block these attempts.
row_errors.append({
'username': username,
'email': email,
'response': _('Invalid email {email_address}.').format(email_address=email),
})
log.warning('Email address %s is associated with a retired user, so course enrollment was ' + # lint-amnesty, pylint: disable=logging-not-lazy
'blocked.', email)
else:
# This email does not yet exist, so we need to create a new account
# If username already exists in the database, then create_and_enroll_user
# will raise an IntegrityError exception.
password = generate_unique_password(generated_passwords)
errors = create_and_enroll_user(
email,
username,
name,
country,
password,
course_id,
course_mode,
request.user,
email_params,
email_user=notify_by_email,
)
row_errors.extend(errors)
if cohort:
try:
add_user_to_cohort(cohort, email)
except ValueError:
# user already in this cohort; ignore
# NOTE: Checking this here may be unnecessary if we can prove that a
# new user will never be
# automatically assigned to a cohort from the above.
pass
except ValidationError:
row_errors.append({
'username': username,
'email': email,
'response': _('Invalid email {email_address}.').format(email_address=email),
})
else:
general_errors.append({
'username': '', 'email': '', 'response': _('File is not attached.')
})
results = {
'row_errors': row_errors,
'general_errors': general_errors,
'warnings': warnings
}
return JsonResponse(results)
def generate_random_string(length):
@@ -979,17 +987,8 @@ def bulk_beta_modify_access(request, course_id):
return JsonResponse(response_payload)
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_course_permission(permissions.EDIT_COURSE_ACCESS)
@require_post_params(
unique_student_identifier="email or username of user to change access",
rolename="'instructor', 'staff', 'beta', or 'ccx_coach'",
action="'allow' or 'revoke'"
)
@common_exceptions_400
def modify_access(request, course_id):
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True), name='dispatch')
class ModifyAccess(APIView):
"""
Modify staff/instructor access of other user.
Requires instructor access.
@@ -1001,77 +1000,83 @@ def modify_access(request, course_id):
rolename is one of ['instructor', 'staff', 'beta', 'ccx_coach']
action is one of ['allow', 'revoke']
"""
course_id = CourseKey.from_string(course_id)
course = get_course_with_access(
request.user, 'instructor', course_id, depth=None
)
unique_student_identifier = request.POST.get('unique_student_identifier')
try:
user = get_student_from_identifier(unique_student_identifier)
except User.DoesNotExist:
response_payload = {
'unique_student_identifier': unique_student_identifier,
'userDoesNotExist': True,
}
return JsonResponse(response_payload)
permission_classes = (IsAuthenticated, permissions.InstructorPermission)
permission_name = permissions.EDIT_COURSE_ACCESS
serializer_class = AccessSerializer
# Check that user is active, because add_users
# in common/djangoapps/student/roles.py fails
# silently when we try to add an inactive user.
if not user.is_active:
response_payload = {
'unique_student_identifier': user.username,
'inactiveUser': True,
}
return JsonResponse(response_payload)
@method_decorator(ensure_csrf_cookie)
def post(self, request, course_id):
"""
Modify staff/instructor access of other user.
Requires instructor access.
"""
course_id = CourseKey.from_string(course_id)
course = get_course_with_access(
request.user, 'instructor', course_id, depth=None
)
rolename = request.POST.get('rolename')
action = request.POST.get('action')
serializer_data = AccessSerializer(data=request.data)
if not serializer_data.is_valid():
return HttpResponseBadRequest(reason=serializer_data.errors)
if rolename not in ROLES:
error = strip_tags(f"unknown rolename '{rolename}'")
log.error(error)
return HttpResponseBadRequest(error)
user = serializer_data.validated_data.get('unique_student_identifier')
if not user:
response_payload = {
'unique_student_identifier': request.data.get('unique_student_identifier'),
'userDoesNotExist': True,
}
return JsonResponse(response_payload)
if not user.is_active:
response_payload = {
'unique_student_identifier': user.username,
'inactiveUser': True,
}
return JsonResponse(response_payload)
rolename = serializer_data.data['rolename']
action = serializer_data.data['action']
if rolename not in ROLES:
error = strip_tags(f"unknown rolename '{rolename}'")
log.error(error)
return HttpResponseBadRequest(error)
# disallow instructors from removing their own instructor access.
if rolename == 'instructor' and user == request.user and action != 'allow':
response_payload = {
'unique_student_identifier': user.username,
'rolename': rolename,
'action': action,
'removingSelfAsInstructor': True,
}
return JsonResponse(response_payload)
if action == 'allow':
allow_access(course, user, rolename)
if not is_user_enrolled_in_course(user, course_id):
CourseEnrollment.enroll(user, course_id)
elif action == 'revoke':
revoke_access(course, user, rolename)
else:
return HttpResponseBadRequest(strip_tags(
f"unrecognized action u'{action}'"
))
# disallow instructors from removing their own instructor access.
if rolename == 'instructor' and user == request.user and action != 'allow':
response_payload = {
'unique_student_identifier': user.username,
'rolename': rolename,
'action': action,
'removingSelfAsInstructor': True,
'success': 'yes',
}
return JsonResponse(response_payload)
if action == 'allow':
allow_access(course, user, rolename)
if not is_user_enrolled_in_course(user, course_id):
CourseEnrollment.enroll(user, course_id)
elif action == 'revoke':
revoke_access(course, user, rolename)
else:
return HttpResponseBadRequest(strip_tags(
f"unrecognized action u'{action}'"
))
response_payload = {
'unique_student_identifier': user.username,
'rolename': rolename,
'action': action,
'success': 'yes',
}
return JsonResponse(response_payload)
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_course_permission(permissions.EDIT_COURSE_ACCESS)
@require_post_params(rolename="'instructor', 'staff', or 'beta'")
def list_course_role_members(request, course_id):
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True), name='dispatch')
class ListCourseRoleMembersView(APIView):
"""
List instructors and staff.
Requires instructor access.
View to list instructors and staff for a specific course.
Requires the user to have instructor access.
rolename is one of ['instructor', 'staff', 'beta', 'ccx_coach']
@@ -1087,33 +1092,41 @@ def list_course_role_members(request, course_id):
]
}
"""
course_id = CourseKey.from_string(course_id)
course = get_course_with_access(
request.user, 'instructor', course_id, depth=None
)
permission_classes = (IsAuthenticated, permissions.InstructorPermission)
permission_name = permissions.EDIT_COURSE_ACCESS
rolename = request.POST.get('rolename')
@method_decorator(ensure_csrf_cookie)
def post(self, request, course_id):
"""
Handles POST request to list instructors and staff.
if rolename not in ROLES:
return HttpResponseBadRequest()
Args:
request (HttpRequest): The request object containing user data.
course_id (str): The ID of the course to list instructors and staff for.
def extract_user_info(user):
""" convert user into dicts for json view """
Returns:
Response: A Response object containing the list of instructors and staff or an error message.
return {
'username': user.username,
'email': user.email,
'first_name': user.first_name,
'last_name': user.last_name,
Raises:
Http404: If the course does not exist.
"""
course_id = CourseKey.from_string(course_id)
course = get_course_with_access(
request.user, 'instructor', course_id, depth=None
)
role_serializer = RoleNameSerializer(data=request.data)
role_serializer.is_valid(raise_exception=True)
rolename = role_serializer.data['rolename']
users = list_with_level(course.id, rolename)
serializer = UserSerializer(users, many=True)
response_payload = {
'course_id': str(course_id),
rolename: serializer.data,
}
response_payload = {
'course_id': str(course_id),
rolename: list(map(extract_user_info, list_with_level(
course.id, rolename
))),
}
return JsonResponse(response_payload)
return Response(response_payload, status=status.HTTP_200_OK)
class ProblemResponseReportPostParamsSerializer(serializers.Serializer): # pylint: disable=abstract-method
@@ -1718,15 +1731,35 @@ def get_student_enrollment_status(request, course_id):
return JsonResponse(response_payload)
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_course_permission(permissions.ENROLLMENT_REPORT)
@require_post_params(
unique_student_identifier="email or username of student for whom to get progress url"
)
@common_exceptions_400
def get_student_progress_url(request, course_id):
class StudentProgressUrlSerializer(serializers.Serializer):
"""Serializer for course renders"""
unique_student_identifier = serializers.CharField(write_only=True)
course_id = CourseKeyField(required=False)
progress_url = serializers.SerializerMethodField()
def get_progress_url(self, obj): # pylint: disable=unused-argument
"""
Return the progress URL for the student.
Args:
obj (dict): The dictionary containing data for the serializer.
Returns:
str: The URL for the progress of the student in the course.
"""
user = get_student_from_identifier(obj.get('unique_student_identifier'))
course_id = obj.get('course_id') # Adjust based on your data structure
if course_home_mfe_progress_tab_is_active(course_id):
progress_url = get_learning_mfe_home_url(course_id, url_fragment='progress')
if user is not None:
progress_url += '/{}/'.format(user.id)
else:
progress_url = reverse('student_progress', kwargs={'course_id': str(course_id), 'student_id': user.id})
return progress_url
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True), name='dispatch')
class StudentProgressUrl(APIView):
"""
Get the progress url of a student.
Limited to staff access.
@@ -1736,21 +1769,25 @@ def get_student_progress_url(request, course_id):
'progress_url': '/../...'
}
"""
course_id = CourseKey.from_string(course_id)
user = get_student_from_identifier(request.POST.get('unique_student_identifier'))
authentication_classes = (
JwtAuthentication,
BearerAuthenticationAllowInactiveUser,
SessionAuthenticationAllowInactiveUser,
)
permission_classes = (IsAuthenticated, permissions.InstructorPermission)
serializer_class = StudentProgressUrlSerializer
permission_name = permissions.ENROLLMENT_REPORT
if course_home_mfe_progress_tab_is_active(course_id):
progress_url = get_learning_mfe_home_url(course_id, url_fragment='progress')
if user is not None:
progress_url += '/{}/'.format(user.id)
else:
progress_url = reverse('student_progress', kwargs={'course_id': str(course_id), 'student_id': user.id})
response_payload = {
'course_id': str(course_id),
'progress_url': progress_url,
}
return JsonResponse(response_payload)
@method_decorator(ensure_csrf_cookie)
def post(self, request, course_id):
"""Post method for validating incoming data and generating progress URL"""
data = {
'course_id': course_id,
'unique_student_identifier': request.data.get('unique_student_identifier')
}
serializer = self.serializer_class(data=data)
serializer.is_valid(raise_exception=True)
return Response(serializer.data)
@transaction.non_atomic_requests
@@ -2135,23 +2172,35 @@ def list_background_email_tasks(request, course_id):
return JsonResponse(response_payload)
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_course_permission(permissions.EMAIL)
def list_email_content(request, course_id):
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True), name='dispatch')
class ListEmailContent(APIView):
"""
List the content of bulk emails sent
"""
course_id = CourseKey.from_string(course_id)
task_type = InstructorTaskTypes.BULK_COURSE_EMAIL
# First get tasks list of bulk emails sent
emails = task_api.get_instructor_task_history(course_id, task_type=task_type)
permission_classes = (IsAuthenticated, permissions.InstructorPermission)
permission_name = permissions.EMAIL
response_payload = {
'emails': list(map(extract_email_features, emails)),
}
return JsonResponse(response_payload)
@method_decorator(ensure_csrf_cookie)
def post(self, request, course_id):
"""
List the content of bulk emails sent for a specific course.
Args:
request (HttpRequest): The HTTP request object.
course_id (str): The ID of the course for which to list the bulk emails.
Returns:
HttpResponse: A response object containing the list of bulk email contents.
"""
course_id = CourseKey.from_string(course_id)
task_type = InstructorTaskTypes.BULK_COURSE_EMAIL
# First get tasks list of bulk emails sent
emails = task_api.get_instructor_task_history(course_id, task_type=task_type)
response_payload = {
'emails': list(map(extract_email_features, emails)),
}
return JsonResponse(response_payload)
class InstructorTaskSerializer(serializers.Serializer): # pylint: disable=abstract-method
@@ -2341,46 +2390,51 @@ def _list_instructor_tasks(request, course_id):
return JsonResponse(response_payload)
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_course_permission(permissions.SHOW_TASKS)
def list_entrance_exam_instructor_tasks(request, course_id):
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True), name='dispatch')
class ListEntranceExamInstructorTasks(APIView):
"""
List entrance exam related instructor tasks.
Takes either of the following query parameters
- unique_student_identifier is an email or username
- all_students is a boolean
"""
course_id = CourseKey.from_string(course_id)
course = get_course_by_id(course_id)
student = request.POST.get('unique_student_identifier', None)
if student is not None:
student = get_student_from_identifier(student)
permission_classes = (IsAuthenticated, permissions.InstructorPermission)
permission_name = permissions.SHOW_TASKS
try:
entrance_exam_key = UsageKey.from_string(course.entrance_exam_id).map_into_course(course_id)
except InvalidKeyError:
return HttpResponseBadRequest(_("Course has no valid entrance exam section."))
if student:
# Specifying for a single student's entrance exam history
tasks = task_api.get_entrance_exam_instructor_task_history(
course_id,
entrance_exam_key,
student
)
else:
# Specifying for all student's entrance exam history
tasks = task_api.get_entrance_exam_instructor_task_history(
course_id,
entrance_exam_key
)
@method_decorator(ensure_csrf_cookie)
def post(self, request, course_id):
"""
List entrance exam related instructor tasks.
response_payload = {
'tasks': list(map(extract_task_features, tasks)),
}
return JsonResponse(response_payload)
Takes either of the following query parameters
- unique_student_identifier is an email or username
- all_students is a boolean
"""
course_id = CourseKey.from_string(course_id)
course = get_course_by_id(course_id)
student = request.POST.get('unique_student_identifier', None)
if student is not None:
student = get_student_from_identifier(student)
try:
entrance_exam_key = UsageKey.from_string(course.entrance_exam_id).map_into_course(course_id)
except InvalidKeyError:
return HttpResponseBadRequest(_("Course has no valid entrance exam section."))
if student:
# Specifying for a single student's entrance exam history
tasks = task_api.get_entrance_exam_instructor_task_history(
course_id,
entrance_exam_key,
student
)
else:
# Specifying for all student's entrance exam history
tasks = task_api.get_entrance_exam_instructor_task_history(
course_id,
entrance_exam_key
)
response_payload = {
'tasks': list(map(extract_task_features, tasks)),
}
return JsonResponse(response_payload)
class ReportDownloadSerializer(serializers.Serializer): # pylint: disable=abstract-method

View File

@@ -1,3 +1,4 @@
"""
Instructor API endpoint urls.
"""
@@ -21,9 +22,9 @@ v1_api_urls = [
urlpatterns = [
path('students_update_enrollment', api.students_update_enrollment, name='students_update_enrollment'),
path('register_and_enroll_students', api.register_and_enroll_students, name='register_and_enroll_students'),
path('list_course_role_members', api.list_course_role_members, name='list_course_role_members'),
path('modify_access', api.modify_access, name='modify_access'),
path('register_and_enroll_students', api.RegisterAndEnrollStudents.as_view(), name='register_and_enroll_students'),
path('list_course_role_members', api.ListCourseRoleMembersView.as_view(), name='list_course_role_members'),
path('modify_access', api.ModifyAccess.as_view(), name='modify_access'),
path('bulk_beta_modify_access', api.bulk_beta_modify_access, name='bulk_beta_modify_access'),
path('get_problem_responses', api.get_problem_responses, name='get_problem_responses'),
path('get_grading_config', api.get_grading_config, name='get_grading_config'),
@@ -32,20 +33,20 @@ urlpatterns = [
path('get_students_who_may_enroll', api.get_students_who_may_enroll, name='get_students_who_may_enroll'),
path('get_anon_ids', api.get_anon_ids, name='get_anon_ids'),
path('get_student_enrollment_status', api.get_student_enrollment_status, name="get_student_enrollment_status"),
path('get_student_progress_url', api.get_student_progress_url, name='get_student_progress_url'),
path('get_student_progress_url', api.StudentProgressUrl.as_view(), name='get_student_progress_url'),
path('reset_student_attempts', api.reset_student_attempts, name='reset_student_attempts'),
path('rescore_problem', api.rescore_problem, name='rescore_problem'),
path('override_problem_score', api.override_problem_score, name='override_problem_score'),
path('reset_student_attempts_for_entrance_exam', api.reset_student_attempts_for_entrance_exam,
name='reset_student_attempts_for_entrance_exam'),
path('rescore_entrance_exam', api.rescore_entrance_exam, name='rescore_entrance_exam'),
path('list_entrance_exam_instructor_tasks', api.list_entrance_exam_instructor_tasks,
path('list_entrance_exam_instructor_tasks', api.ListEntranceExamInstructorTasks.as_view(),
name='list_entrance_exam_instructor_tasks'),
path('mark_student_can_skip_entrance_exam', api.mark_student_can_skip_entrance_exam,
name='mark_student_can_skip_entrance_exam'),
path('list_instructor_tasks', api.list_instructor_tasks, name='list_instructor_tasks'),
path('list_background_email_tasks', api.list_background_email_tasks, name='list_background_email_tasks'),
path('list_email_content', api.list_email_content, name='list_email_content'),
path('list_email_content', api.ListEmailContent.as_view(), name='list_email_content'),
path('list_forum_members', api.list_forum_members, name='list_forum_members'),
path('update_forum_role_membership', api.update_forum_role_membership, name='update_forum_role_membership'),
path('send_email', api.send_email, name='send_email'),

View File

@@ -0,0 +1,61 @@
""" Instructor apis serializers. """
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _
from rest_framework import serializers
from .tools import get_student_from_identifier
from lms.djangoapps.instructor.access import ROLES
class RoleNameSerializer(serializers.Serializer): # pylint: disable=abstract-method
"""
Serializer that describes the response of the problem response report generation API.
"""
rolename = serializers.CharField(help_text=_("Role name"))
def validate_rolename(self, value):
"""
Check that the rolename is valid.
"""
if value not in ROLES:
raise ValidationError(_("Invalid role name."))
return value
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'email', 'first_name', 'last_name']
class AccessSerializer(serializers.Serializer):
"""
Serializer for managing user access changes.
This serializer validates and processes the data required to modify
user access within a system.
"""
unique_student_identifier = serializers.CharField(
max_length=255,
help_text="Email or username of user to change access"
)
rolename = serializers.CharField(
help_text="Role name to assign to the user"
)
action = serializers.ChoiceField(
choices=['allow', 'revoke'],
help_text="Action to perform on the user's access"
)
def validate_unique_student_identifier(self, value):
"""
Validate that the unique_student_identifier corresponds to an existing user.
"""
try:
user = get_student_from_identifier(value)
except User.DoesNotExist:
return None
return user

View File

@@ -5,7 +5,7 @@
# List of valid templates is explicitly managed for (short-term)
# security reasons.
import logging
import mimetypes
from django.conf import settings
@@ -23,6 +23,8 @@ from common.djangoapps.util.cache import cache_if_anonymous
from common.djangoapps.util.views import fix_crum_request
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
log = logging.getLogger(__name__)
valid_templates = []
if settings.STATIC_GRAB:
@@ -122,4 +124,21 @@ def render_429(request, exception=None): # lint-amnesty, pylint: disable=unused
@fix_crum_request
def render_500(request):
return HttpResponseServerError(render_to_string('static_templates/server-error.html', {}, request=request))
"""
Render the generic error page when we have an uncaught error.
"""
try:
return HttpResponseServerError(render_to_string('static_templates/server-error.html', {}, request=request))
except BaseException as e:
# If we can't render the error page, ensure we don't raise another
# exception -- because if we do, we'll probably just end up back
# at the same rendering error.
#
# This is an attempt at working around the recursive error handling issues
# observed in <https://github.com/openedx/edx-platform/issues/35151>, which
# were triggered by Mako and translation errors.
log.error("Encountered error while rendering error page.", exc_info=True)
# This message is intentionally hardcoded and does not involve
# any translation, templating, etc. Do not translate.
return HttpResponseServerError("Encountered error while rendering error page.")

View File

@@ -56,7 +56,10 @@ from enterprise.constants import (
ENTERPRISE_FULFILLMENT_OPERATOR_ROLE,
ENTERPRISE_REPORTING_CONFIG_ADMIN_ROLE,
ENTERPRISE_SSO_ORCHESTRATOR_OPERATOR_ROLE,
ENTERPRISE_OPERATOR_ROLE
ENTERPRISE_OPERATOR_ROLE,
SYSTEM_ENTERPRISE_PROVISIONING_ADMIN_ROLE,
PROVISIONING_ENTERPRISE_CUSTOMER_ADMIN_ROLE,
PROVISIONING_PENDING_ENTERPRISE_CUSTOMER_ADMIN_ROLE,
)
from openedx.core.constants import COURSE_KEY_REGEX, COURSE_KEY_PATTERN, COURSE_ID_PATTERN
@@ -1101,6 +1104,11 @@ DEFAULT_GROUPS = []
# If this is true, random scores will be generated for the purpose of debugging the profile graphs
GENERATE_PROFILE_SCORES = False
# .. setting_name: GRADEBOOK_FREEZE_DAYS
# .. setting_default: 30
# .. setting_description: Sets the number of days after which the gradebook will freeze following the course's end.
GRADEBOOK_FREEZE_DAYS = 30
# Used with XQueue
XQUEUE_WAITTIME_BETWEEN_REQUESTS = 5 # seconds
XQUEUE_INTERFACE = {
@@ -4740,6 +4748,10 @@ SYSTEM_TO_FEATURE_ROLE_MAPPING = {
ENTERPRISE_FULFILLMENT_OPERATOR_ROLE,
ENTERPRISE_SSO_ORCHESTRATOR_OPERATOR_ROLE,
],
SYSTEM_ENTERPRISE_PROVISIONING_ADMIN_ROLE: [
PROVISIONING_ENTERPRISE_CUSTOMER_ADMIN_ROLE,
PROVISIONING_PENDING_ENTERPRISE_CUSTOMER_ADMIN_ROLE,
],
}
DATA_CONSENT_SHARE_CACHE_TIMEOUT = 8 * 60 * 60 # 8 hours

View File

@@ -739,8 +739,8 @@ describe('Program Details View', () => {
);
});
it('should render appropriate subscription text when subscription is active with trial', () => {
testSubscriptionState(
it('should not render appropriate subscription text when subscription is active with trial', () => {
testSubscriptionSunsetting(
'active',
'Manage my subscription',
'Trial ends',
@@ -748,8 +748,8 @@ describe('Program Details View', () => {
);
});
it('should render appropriate subscription text when subscription is active', () => {
testSubscriptionState(
it('should not render appropriate subscription text when subscription is active', () => {
testSubscriptionSunsetting(
'active',
'Manage my subscription',
'Your next billing date is',

View File

@@ -1,5 +1,7 @@
// LMS-specific variables
@import '_builtin-block-variables';
$text-width-readability-max: 1080px;
// LMS-only colors

View File

@@ -46,64 +46,7 @@
</div>
</div>
<% } %>
<% if (isSubscriptionEligible && (
completedCount !== totalCount
|| subscriptionState === 'active'
)
) { %>
<div class="d-flex flex-column align-items-start flex-xl-row align-items-xl-center upgrade-subscription">
<a
href="<%- subscriptionUrl %>"
class="js-subscription-cta btn-brand btn cta-primary upgrade-button"
<% if (subscriptionState === 'active') { %>
target="_blank"
rel="noopener noreferrer"
<% } %>
>
<% if (subscriptionState === 'active') { %>
<div class="d-flex align-items-center">
<span><%- gettext('Manage my subscription') %></span>
<div class="subscription-icon-launch">
<% // xss-lint: disable=underscore-not-escaped %>
<%= launchIcon %>
</div>
</div>
<% } else if (subscriptionState === 'inactive') { %>
<div class="d-flex align-items-center">
<div class="subscription-icon-restart">
<% // xss-lint: disable=underscore-not-escaped %>
<%= restartIcon %>
</div>
<span><%- gettext('Restart my subscription') %></span>
</div>
<% } else { %>
<%- StringUtils.interpolate(
gettext('Start {trialLength}-day free trial'),
{ trialLength }
) %>
<% } %>
</a>
<span class="subscription-info-brief">
<%- StringUtils.interpolate(
(
hasActiveTrial
? gettext('Trial ends {trialEndDate} at {trialEndTime}')
: subscriptionState === 'active'
? gettext('Your next billing date is {currentPeriodEnd}')
: subscriptionState === 'inactive'
? gettext('{subscriptionPrice} subscription. Cancel anytime.')
: gettext('{subscriptionPrice} subscription after trial ends. Cancel anytime.')
),
{
subscriptionPrice,
currentPeriodEnd,
trialEndDate,
trialEndTime,
}
) %>
</span>
</div>
<% } else if (
<% if (
!isSubscriptionEligible
&& is_learner_eligible_for_one_click_purchase
&& (typeof is_mobile_only === 'undefined' || is_mobile_only === false)

View File

@@ -21,64 +21,7 @@
</div>
</div>
<% } %>
<% if (isSubscriptionEligible && (
completedCount !== totalCount
|| subscriptionState === 'active'
)
) { %>
<div class="d-flex flex-column align-items-start flex-xl-row align-items-xl-center upgrade-subscription">
<a
href="<%- subscriptionUrl %>"
class="js-subscription-cta btn-brand btn cta-primary upgrade-button"
<% if (subscriptionState === 'active') { %>
target="_blank"
rel="noopener noreferrer"
<% } %>
>
<% if (subscriptionState === 'active') { %>
<div class="d-flex align-items-center">
<span><%- gettext('Manage my subscription') %></span>
<div class="subscription-icon-launch">
<% // xss-lint: disable=underscore-not-escaped %>
<%= launchIcon %>
</div>
</div>
<% } else if (subscriptionState === 'inactive') { %>
<div class="d-flex align-items-center">
<div class="subscription-icon-restart">
<% // xss-lint: disable=underscore-not-escaped %>
<%= restartIcon %>
</div>
<span><%- gettext('Restart my subscription') %></span>
</div>
<% } else { %>
<%- StringUtils.interpolate(
gettext('Start {trialLength}-day free trial'),
{ trialLength }
) %>
<% } %>
</a>
<span class="subscription-info-brief">
<%- StringUtils.interpolate(
(
hasActiveTrial
? gettext('Trial ends {trialEndDate} at {trialEndTime}')
: subscriptionState === 'active'
? gettext('Your next billing date is {currentPeriodEnd}')
: subscriptionState === 'inactive'
? gettext('{subscriptionPrice} subscription. Cancel anytime.')
: gettext('{subscriptionPrice} subscription after trial ends. Cancel anytime.')
),
{
subscriptionPrice,
currentPeriodEnd,
trialEndDate,
trialEndTime,
}
) %>
</span>
</div>
<% } else if (
<% if (
!isSubscriptionEligible
&& is_learner_eligible_for_one_click_purchase
&& (typeof is_mobile_only === 'undefined' || is_mobile_only === false)

View File

@@ -467,6 +467,29 @@ def delete_index_doc(usage_key: UsageKey) -> None:
_wait_for_meili_tasks(tasks)
def delete_all_draft_docs_for_library(library_key: LibraryLocatorV2) -> None:
"""
Deletes draft documents for the given XBlocks from the search index
"""
current_rebuild_index_name = _get_running_rebuild_index_name()
client = _get_meilisearch_client()
# Delete all documents where last_published is null i.e. never published before.
delete_filter = [
f'{Fields.context_key}="{library_key}"',
# This field should only be NULL or have a value, but we're also checking IS EMPTY just in case.
# Inner arrays are connected by an OR
[f'{Fields.last_published} IS EMPTY', f'{Fields.last_published} IS NULL'],
]
tasks = []
if current_rebuild_index_name:
# If there is a rebuild in progress, the documents will also be deleted from the new index.
tasks.append(client.index(current_rebuild_index_name).delete_documents(filter=delete_filter))
tasks.append(client.index(STUDIO_INDEX_NAME).delete_documents(filter=delete_filter))
_wait_for_meili_tasks(tasks)
def upsert_library_block_index_doc(usage_key: UsageKey) -> None:
"""
Creates or updates the document for the given Library Block in the search index

View File

@@ -9,9 +9,9 @@ import logging
from celery import shared_task
from celery_utils.logged_task import LoggedTask
from edx_django_utils.monitoring import set_code_owner_attribute
from meilisearch.errors import MeilisearchError
from opaque_keys.edx.keys import UsageKey
from opaque_keys.edx.locator import LibraryLocatorV2, LibraryUsageLocatorV2
from meilisearch.errors import MeilisearchError
from . import api
@@ -81,3 +81,6 @@ def update_content_library_index_docs(library_key_str: str) -> None:
log.info("Updating content index documents for library with id: %s", library_key)
api.upsert_content_library_index_docs(library_key)
# Delete all documents in this library that were not published by above function
# as this task is also triggered on discard event.
api.delete_all_draft_docs_for_library(library_key)

View File

@@ -388,3 +388,18 @@ class TestSearchApi(ModuleStoreTestCase):
mock_meilisearch.return_value.index.return_value.update_documents.assert_called_once_with(
[self.doc_problem1, self.doc_problem2]
)
@override_settings(MEILISEARCH_ENABLED=True)
def test_delete_all_drafts(self, mock_meilisearch):
"""
Test deleting all draft documents from the index.
"""
api.delete_all_draft_docs_for_library(self.library.key)
delete_filter = [
f'context_key="{self.library.key}"',
['last_published IS EMPTY', 'last_published IS NULL'],
]
mock_meilisearch.return_value.index.return_value.delete_documents.assert_called_once_with(
filter=delete_filter
)

View File

@@ -156,6 +156,9 @@ class ContentLibraryMetadata:
version = attr.ib(0)
type = attr.ib(default=COMPLEX)
last_published = attr.ib(default=None, type=datetime)
last_draft_created = attr.ib(default=None, type=datetime)
last_draft_created_by = attr.ib(default=None, type=datetime)
published_by = attr.ib("")
has_unpublished_changes = attr.ib(False)
# has_unpublished_deletes will be true when the draft version of the library's bundle
# contains deletes of any XBlocks that were in the most recently published version
@@ -168,6 +171,8 @@ class ContentLibraryMetadata:
# Studio, use it in their courses, and copy content out of this library.
allow_public_read = attr.ib(False)
license = attr.ib("")
created = attr.ib(default=None, type=datetime)
updated = attr.ib(default=None, type=datetime)
class AccessLevel:
@@ -206,7 +211,7 @@ class LibraryXBlockMetadata:
"""
Construct a LibraryXBlockMetadata from a Component object.
"""
last_publish_log = authoring_api.get_last_publish(component.pk)
last_publish_log = component.versioning.last_publish_log
return cls(
usage_key=LibraryUsageLocatorV2(
@@ -350,8 +355,11 @@ def get_library(library_key):
learning_package = ref.learning_package
num_blocks = authoring_api.get_all_drafts(learning_package.id).count()
last_publish_log = authoring_api.get_last_publish(learning_package.id)
has_unpublished_changes = authoring_api.get_entities_with_unpublished_changes(learning_package.id) \
.exists()
last_draft_log = authoring_api.get_entities_with_unpublished_changes(learning_package.id) \
.order_by('-created').first()
last_draft_created = last_draft_log.created if last_draft_log else None
last_draft_created_by = last_draft_log.created_by.username if last_draft_log and last_draft_log.created_by else None
has_unpublished_changes = last_draft_log is not None
# TODO: I'm doing this one to match already-existing behavior, but this is
# something that we should remove. It exists to accomodate some complexities
@@ -377,6 +385,9 @@ def get_library(library_key):
# libraries. The top level version stays for now because LibraryContentBlock
# uses it, but that should hopefully change before the Redwood release.
version = 0 if last_publish_log is None else last_publish_log.pk
published_by = None
if last_publish_log and last_publish_log.published_by:
published_by = last_publish_log.published_by.username
return ContentLibraryMetadata(
key=library_key,
@@ -386,12 +397,17 @@ def get_library(library_key):
num_blocks=num_blocks,
version=version,
last_published=None if last_publish_log is None else last_publish_log.published_at,
published_by=published_by,
last_draft_created=last_draft_created,
last_draft_created_by=last_draft_created_by,
allow_lti=ref.allow_lti,
allow_public_learning=ref.allow_public_learning,
allow_public_read=ref.allow_public_read,
has_unpublished_changes=has_unpublished_changes,
has_unpublished_deletes=has_unpublished_deletes,
license=ref.license,
created=learning_package.created,
updated=learning_package.updated,
)
@@ -735,27 +751,30 @@ def set_library_block_olx(usage_key, new_olx_str):
)
def create_library_block(library_key, block_type, definition_id):
def validate_can_add_block_to_library(
library_key: LibraryLocatorV2,
block_type: str,
block_id: str,
) -> tuple[ContentLibrary, LibraryUsageLocatorV2]:
"""
Create a new XBlock in this library of the specified type (e.g. "html").
"""
# It's in the serializer as ``definition_id``, but for our purposes, it's
# the block_id. See the comments in ``LibraryXBlockCreationSerializer`` for
# more details. TODO: Change the param name once we change the serializer.
block_id = definition_id
Perform checks to validate whether a new block with `block_id` and type `block_type` can be added to
the library with key `library_key`.
Returns the ContentLibrary that has the passed in `library_key` and newly created LibraryUsageLocatorV2 if
validation successful, otherwise raises errors.
"""
assert isinstance(library_key, LibraryLocatorV2)
ref = ContentLibrary.objects.get_by_key(library_key)
if ref.type != COMPLEX:
if block_type != ref.type:
content_library = ContentLibrary.objects.get_by_key(library_key) # type: ignore[attr-defined]
if content_library.type != COMPLEX:
if block_type != content_library.type:
raise IncompatibleTypesError(
_('Block type "{block_type}" is not compatible with library type "{library_type}".').format(
block_type=block_type, library_type=ref.type,
block_type=block_type, library_type=content_library.type,
)
)
# If adding a component would take us over our max, return an error.
component_count = authoring_api.get_all_drafts(ref.learning_package.id).count()
component_count = authoring_api.get_all_drafts(content_library.learning_package.id).count()
if component_count + 1 > settings.MAX_BLOCKS_PER_CONTENT_LIBRARY:
raise BlockLimitReachedError(
_("Library cannot have more than {} Components").format(
@@ -768,7 +787,7 @@ def create_library_block(library_key, block_type, definition_id):
# Ensure the XBlock type is valid and installed:
XBlock.load_class(block_type) # Will raise an exception if invalid
# Make sure the new ID is not taken already:
usage_key = LibraryUsageLocatorV2(
usage_key = LibraryUsageLocatorV2( # type: ignore[abstract]
lib_key=library_key,
block_type=block_type,
usage_id=block_id,
@@ -777,12 +796,26 @@ def create_library_block(library_key, block_type, definition_id):
if _component_exists(usage_key):
raise LibraryBlockAlreadyExists(f"An XBlock with ID '{usage_key}' already exists")
_create_component_for_block(ref, usage_key)
return content_library, usage_key
def create_library_block(library_key, block_type, definition_id, user_id=None):
"""
Create a new XBlock in this library of the specified type (e.g. "html").
"""
# It's in the serializer as ``definition_id``, but for our purposes, it's
# the block_id. See the comments in ``LibraryXBlockCreationSerializer`` for
# more details. TODO: Change the param name once we change the serializer.
block_id = definition_id
content_library, usage_key = validate_can_add_block_to_library(library_key, block_type, block_id)
_create_component_for_block(content_library, usage_key, user_id)
# Now return the metadata about the new block:
LIBRARY_BLOCK_CREATED.send_event(
library_block=LibraryBlockData(
library_key=ref.library_key,
library_key=content_library.library_key,
usage_key=usage_key
)
)
@@ -804,6 +837,46 @@ def _component_exists(usage_key: UsageKeyV2) -> bool:
return True
def import_staged_content_from_user_clipboard(library_key: LibraryLocatorV2, user, block_id) -> XBlock:
"""
Create a new library block and populate it with staged content from clipboard
Returns the newly created library block
"""
from openedx.core.djangoapps.content_staging import api as content_staging_api
if not content_staging_api:
raise RuntimeError("The required content_staging app is not installed")
user_clipboard = content_staging_api.get_user_clipboard(user)
if not user_clipboard:
return None
olx_str = content_staging_api.get_staged_content_olx(user_clipboard.content.id)
# TODO: Handle importing over static assets
content_library, usage_key = validate_can_add_block_to_library(
library_key,
user_clipboard.content.block_type,
block_id
)
# Create component for block then populate it with clipboard data
_create_component_for_block(content_library, usage_key, user.id)
set_library_block_olx(usage_key, olx_str)
# Emit library block created event
LIBRARY_BLOCK_CREATED.send_event(
library_block=LibraryBlockData(
library_key=content_library.library_key,
usage_key=usage_key
)
)
# Now return the metadata about the new block
return get_library_block(usage_key)
def get_or_create_olx_media_type(block_type: str) -> MediaType:
"""
Get or create a MediaType for the block type.
@@ -816,7 +889,7 @@ def get_or_create_olx_media_type(block_type: str) -> MediaType:
)
def _create_component_for_block(content_lib, usage_key):
def _create_component_for_block(content_lib, usage_key, user_id=None):
"""
Create a Component for an XBlock type, and initialize it.
@@ -847,7 +920,7 @@ def _create_component_for_block(content_lib, usage_key):
local_key=usage_key.block_id,
title=display_name,
created=now,
created_by=None,
created_by=user_id,
)
content = authoring_api.get_or_create_text_content(
learning_package.id,
@@ -951,13 +1024,13 @@ def get_allowed_block_types(library_key): # pylint: disable=unused-argument
return info
def publish_changes(library_key):
def publish_changes(library_key, user_id=None):
"""
Publish all pending changes to the specified library.
"""
learning_package = ContentLibrary.objects.get_by_key(library_key).learning_package
authoring_api.publish_all_drafts(learning_package.id)
authoring_api.publish_all_drafts(learning_package.id, published_by=user_id)
CONTENT_LIBRARY_UPDATED.send_event(
content_library=ContentLibraryData(

View File

@@ -36,12 +36,14 @@ class ContentLibraryMetadataSerializer(serializers.Serializer):
org = serializers.SlugField(source="key.org")
slug = serializers.CharField(source="key.slug", validators=(validate_unicode_slug, ))
bundle_uuid = serializers.UUIDField(format='hex_verbose', read_only=True)
#collection_uuid = serializers.UUIDField(format='hex_verbose', write_only=True)
title = serializers.CharField()
description = serializers.CharField(allow_blank=True)
num_blocks = serializers.IntegerField(read_only=True)
version = serializers.IntegerField(read_only=True)
last_published = serializers.DateTimeField(format=DATETIME_FORMAT, read_only=True)
published_by = serializers.CharField(read_only=True)
last_draft_created = serializers.DateTimeField(format=DATETIME_FORMAT, read_only=True)
last_draft_created_by = serializers.CharField(read_only=True)
allow_lti = serializers.BooleanField(default=False, read_only=True)
allow_public_learning = serializers.BooleanField(default=False)
allow_public_read = serializers.BooleanField(default=False)
@@ -49,6 +51,8 @@ class ContentLibraryMetadataSerializer(serializers.Serializer):
has_unpublished_deletes = serializers.BooleanField(read_only=True)
license = serializers.ChoiceField(choices=LICENSE_OPTIONS, default=ALL_RIGHTS_RESERVED)
can_edit_library = serializers.SerializerMethodField()
created = serializers.DateTimeField(format=DATETIME_FORMAT, read_only=True)
updated = serializers.DateTimeField(format=DATETIME_FORMAT, read_only=True)
def get_can_edit_library(self, obj):
"""
@@ -175,6 +179,17 @@ class LibraryXBlockCreationSerializer(serializers.Serializer):
# slugs at the moment, but hopefully we can change this soon.
definition_id = serializers.CharField(validators=(validate_unicode_slug, ))
# Optional param specified when pasting data from clipboard instead of
# creating new block from scratch
staged_content = serializers.CharField(required=False)
class LibraryPasteClipboardSerializer(serializers.Serializer):
"""
Serializer for pasting clipboard data into library
"""
block_id = serializers.CharField(validators=(validate_unicode_slug, ))
class LibraryXBlockOlxSerializer(serializers.Serializer):
"""

View File

@@ -25,6 +25,7 @@ URL_LIB_BLOCKS = URL_LIB_DETAIL + 'blocks/' # Get the list of XBlocks in this l
URL_LIB_TEAM = URL_LIB_DETAIL + 'team/' # Get the list of users/groups authorized to use this library
URL_LIB_TEAM_USER = URL_LIB_TEAM + 'user/{username}/' # Add/edit/remove a user's permission to use this library
URL_LIB_TEAM_GROUP = URL_LIB_TEAM + 'group/{group_name}/' # Add/edit/remove a group's permission to use this library
URL_LIB_PASTE_CLIPBOARD = URL_LIB_DETAIL + 'paste_clipboard/' # Paste user clipboard (POST) containing Xblock data
URL_LIB_BLOCK = URL_PREFIX + 'blocks/{block_key}/' # Get data about a block, or delete it
URL_LIB_BLOCK_OLX = URL_LIB_BLOCK + 'olx/' # Get or set the OLX of the specified XBlock
URL_LIB_BLOCK_ASSETS = URL_LIB_BLOCK + 'assets/' # List the static asset files of the specified XBlock
@@ -284,6 +285,12 @@ class ContentLibrariesRestApiTest(APITransactionTestCase):
url = URL_LIB_BLOCK_ASSET_FILE.format(block_key=block_key, file_name=file_name)
return self._api('delete', url, None, expect_response)
def _paste_clipboard_content_in_library(self, lib_key, block_id, expect_response=200):
""" Paste's the users clipboard content into Library """
url = URL_LIB_PASTE_CLIPBOARD.format(lib_key=lib_key)
data = {"block_id": block_id}
return self._api('post', url, data, expect_response)
def _render_block_view(self, block_key, view_name, expect_response=200):
"""
Render an XBlock's view in the active application's runtime.

View File

@@ -5,6 +5,7 @@ from unittest.mock import Mock, patch
from unittest import skip
import ddt
from uuid import uuid4
from django.contrib.auth.models import Group
from django.test.client import Client
from organizations.models import Organization
@@ -29,6 +30,7 @@ from openedx.core.djangoapps.content_libraries.tests.base import (
URL_BLOCK_XBLOCK_HANDLER,
)
from openedx.core.djangoapps.content_libraries.constants import VIDEO, COMPLEX, PROBLEM, CC_4_BY
from openedx.core.djangoapps.xblock import api as xblock_api
from openedx.core.djangolib.testing.utils import skip_unless_cms
from common.djangoapps.student.tests.factories import UserFactory
@@ -974,6 +976,52 @@ class ContentLibrariesTestCase(ContentLibrariesRestApiTest, OpenEdxEventsTestMix
event_receiver.call_args.kwargs
)
def test_library_paste_clipboard(self):
"""
Check the a new block is created in the library after pasting from clipboard.
The content of the new block should match the content of the block in the clipboard.
"""
# Importing here since this was failing when tests ran in the LMS
from openedx.core.djangoapps.content_staging.api import save_xblock_to_user_clipboard
# Create user to perform tests on
author = UserFactory.create(username="Author", email="author@example.com")
with self.as_user(author):
lib = self._create_library(
slug="test_lib_paste_clipboard",
title="Paste Clipboard Test Library",
description="Testing pasting clipboard in library"
)
lib_id = lib["id"]
# Add a 'problem' XBlock to the library:
block_data = self._add_block_to_library(lib_id, "problem", "problem1")
# Get the usage_key of the created block
library_key = LibraryLocatorV2.from_string(lib_id)
usage_key = LibraryUsageLocatorV2(
lib_key=library_key,
block_type="problem",
usage_id="problem1"
)
# Get the XBlock created in the previous step
block = xblock_api.load_block(usage_key, user=author)
# Copy the block to the user's clipboard
save_xblock_to_user_clipboard(block, author.id)
# Paste the content of the clipboard into the library
pasted_block_id = str(uuid4())
paste_data = self._paste_clipboard_content_in_library(lib_id, pasted_block_id)
# Check that the new block was created after the paste and it's content matches
# the the block in the clipboard
self.assertDictContainsEntries(self._get_library_block(paste_data["id"]), {
**block_data,
"id": f"lb:CL-TEST:test_lib_paste_clipboard:problem:{pasted_block_id}",
})
@ddt.ddt
class ContentLibraryXBlockValidationTest(APITestCase):

View File

@@ -43,6 +43,8 @@ urlpatterns = [
path('team/group/<str:group_name>/', views.LibraryTeamGroupView.as_view()),
# Import blocks into this library.
path('import_blocks/', include(import_blocks_router.urls)),
# Paste contents of clipboard into library
path('paste_clipboard/', views.LibraryPasteClipboardView.as_view()),
])),
path('blocks/<str:usage_key_str>/', include([
# Get metadata about a specific XBlock in this library, or delete the block:

View File

@@ -112,6 +112,7 @@ from openedx.core.djangoapps.content_libraries.serializers import (
LibraryXBlockStaticFileSerializer,
LibraryXBlockStaticFilesSerializer,
ContentLibraryAddPermissionByEmailSerializer,
LibraryPasteClipboardSerializer,
)
import openedx.core.djangoapps.site_configuration.helpers as configuration_helpers
from openedx.core.lib.api.view_utils import view_auth_classes
@@ -487,7 +488,7 @@ class LibraryCommitView(APIView):
"""
key = LibraryLocatorV2.from_string(lib_key_str)
api.require_permission_for_library_key(key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY)
api.publish_changes(key)
api.publish_changes(key, request.user.id)
return Response({})
@convert_exceptions
@@ -502,6 +503,34 @@ class LibraryCommitView(APIView):
return Response({})
@method_decorator(non_atomic_requests, name="dispatch")
@view_auth_classes()
class LibraryPasteClipboardView(GenericAPIView):
"""
Paste content of clipboard into Library.
"""
@convert_exceptions
def post(self, request, lib_key_str):
"""
Import the contents of the user's clipboard and paste them into the Library
"""
library_key = LibraryLocatorV2.from_string(lib_key_str)
api.require_permission_for_library_key(library_key, request.user, permissions.CAN_EDIT_THIS_CONTENT_LIBRARY)
serializer = LibraryPasteClipboardSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
try:
result = api.import_staged_content_from_user_clipboard(
library_key, request.user, **serializer.validated_data
)
except api.IncompatibleTypesError as err:
raise ValidationError( # lint-amnesty, pylint: disable=raise-missing-from
detail={'block_type': str(err)},
)
return Response(LibraryXBlockMetadataSerializer(result).data)
@method_decorator(non_atomic_requests, name="dispatch")
@view_auth_classes()
class LibraryBlocksView(GenericAPIView):
@@ -556,7 +585,7 @@ class LibraryBlocksView(GenericAPIView):
# Create a new regular top-level block:
try:
result = api.create_library_block(library_key, **serializer.validated_data)
result = api.create_library_block(library_key, user_id=request.user.id, **serializer.validated_data)
except api.IncompatibleTypesError as err:
raise ValidationError( # lint-amnesty, pylint: disable=raise-missing-from
detail={'block_type': str(err)},

View File

@@ -10,12 +10,14 @@ from django.utils.decorators import method_decorator
import edx_api_doc_tools as apidocs
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import UsageKey
from opaque_keys.edx.locator import CourseLocator
from opaque_keys.edx.locator import CourseLocator, LibraryLocatorV2
from rest_framework.exceptions import NotFound, PermissionDenied, ValidationError
from rest_framework.response import Response
from rest_framework.views import APIView
from common.djangoapps.student.auth import has_studio_read_access
from openedx.core.djangoapps.content_libraries import api as lib_api
from openedx.core.djangoapps.xblock import api as xblock_api
from openedx.core.lib.api.view_utils import view_auth_classes
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
@@ -89,18 +91,31 @@ class ClipboardEndpoint(APIView):
if usage_key.block_type in ('course', 'chapter', 'sequential'):
raise ValidationError('Requested XBlock tree is too large')
course_key = usage_key.context_key
if not isinstance(course_key, CourseLocator):
# In the future, we'll support libraries too but for now we don't.
raise ValidationError('Invalid usage key: not a modulestore course')
# Make sure the user has permission on that course
if not has_studio_read_access(request.user, course_key):
raise PermissionDenied("You must be a member of the course team in Studio to export OLX using this API.")
# Load the block and copy it to the user's clipboard
try:
block = modulestore().get_item(usage_key)
if isinstance(course_key, CourseLocator):
# Make sure the user has permission on that course
if not has_studio_read_access(request.user, course_key):
raise PermissionDenied(
"You must be a member of the course team in Studio to export OLX using this API."
)
block = modulestore().get_item(usage_key)
elif isinstance(course_key, LibraryLocatorV2):
lib_api.require_permission_for_library_key(
course_key,
request.user,
lib_api.permissions.CAN_VIEW_THIS_CONTENT_LIBRARY
)
block = xblock_api.load_block(usage_key, user=None)
else:
raise ValidationError("Invalid usage_key for the content.")
except ItemNotFoundError as exc:
raise NotFound("The requested usage key does not exist.") from exc
clipboard = api.save_xblock_to_user_clipboard(block=block, user_id=request.user.id)
# Return the current clipboard exactly as if GET was called:

View File

@@ -441,7 +441,7 @@ class TestContentTagChildrenExport(TaggedCourseMixin): # type: ignore[misc]
"""
Test if we can export a library
"""
with self.assertNumQueries(11):
with self.assertNumQueries(8):
tagged_library = build_object_tree_with_objecttags(self.library.key, self.all_library_object_tags)
assert tagged_library == self.expected_library_tagged_xblock

View File

@@ -0,0 +1,26 @@
"""
Tests for the view version of course asset serving.
"""
import ddt
from django.test import TestCase
from django.urls import resolve
@ddt.ddt
class UrlsTest(TestCase):
"""
Tests for ensuring that the urlpatterns registered to the view are
appropriate for the URLs that the middleware historically handled.
"""
@ddt.data(
'/c4x/edX/Open_DemoX/asset/images_course_image.jpg',
'/asset-v1:edX+DemoX.1+2T2019+type@asset+block/DemoX-poster.jpg',
'/assets/courseware/v1/0123456789abcdef0123456789abcdef/asset-v1:edX+FAKE101+2024+type@asset+block/HW1.png',
)
def test_sample_urls(self, sample_url):
"""
Regression test -- c4x URL was previously incorrect in urls.py.
"""
assert resolve(sample_url).view_name == 'openedx.core.djangoapps.contentserver.views.course_assets_view'

View File

@@ -2,7 +2,7 @@
URL patterns for course asset serving.
"""
from django.urls import path, re_path
from django.urls import re_path
from . import views
@@ -10,7 +10,7 @@ from . import views
# components of the URLs. That's because the view itself is separately
# parsing the paths, for historical reasons. See docstring on views.py.
urlpatterns = [
path("c4x/", views.course_assets_view),
re_path("^c4x/", views.course_assets_view),
re_path("^asset-v1:", views.course_assets_view),
re_path("^assets/courseware/", views.course_assets_view),
]

View File

@@ -7,6 +7,7 @@ from .email_notifications import EmailCadence
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole
from .utils import find_app_in_normalized_apps, find_pref_in_normalized_prefs
from ..django_comment_common.models import FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA
from .notification_content import get_notification_type_content_function
FILTER_AUDIT_EXPIRED_USERS_WITH_NO_ROLE = 'filter_audit_expired_users_with_no_role'
@@ -109,8 +110,9 @@ COURSE_NOTIFICATION_TYPES = {
'is_core': True,
'info': '',
'non_editable': [],
'content_template': _('<{p}><{strong}>{replier_name}</{strong}> commented on {author_name}\'s response in '
'a post youre following <{strong}>{post_title}</{strong}></{p}>'),
'content_template': _('<{p}><{strong}>{replier_name}</{strong}> commented on <{strong}>{author_name}'
'</{strong}> response in a post youre following <{strong}>{post_title}'
'</{strong}></{p}>'),
'content_context': {
'post_title': 'Post title',
'author_name': 'author name',
@@ -169,13 +171,13 @@ COURSE_NOTIFICATION_TYPES = {
'email_template': '',
'filters': [FILTER_AUDIT_EXPIRED_USERS_WITH_NO_ROLE]
},
'course_update': {
'course_updates': {
'notification_app': 'updates',
'name': 'course_update',
'name': 'course_updates',
'is_core': False,
'info': '',
'web': True,
'email': True,
'email': False,
'push': True,
'email_cadence': EmailCadence.DAILY,
'non_editable': [],
@@ -197,7 +199,7 @@ COURSE_NOTIFICATION_TYPES = {
'push': False,
'email_cadence': EmailCadence.DAILY,
'non_editable': [],
'content_template': _('<{p}>You have a new open response submission awaiting for review for : '
'content_template': _('<{p}>You have a new open response submission awaiting for review for '
'<{strong}>{ora_name}</{strong}></{p}>'),
'content_context': {
'ora_name': 'Name of ORA in course',
@@ -465,8 +467,13 @@ def get_notification_content(notification_type, context):
'strong': 'strong',
'p': 'p',
}
content_function = get_notification_type_content_function(notification_type)
if notification_type == 'course_update':
notification_type = 'course_updates'
notification_type = NotificationTypeManager().notification_types.get(notification_type, None)
if notification_type:
if content_function:
return content_function(notification_type, context)
notification_type_content_template = notification_type.get('content_template', None)
if notification_type_content_template:
return notification_type_content_template.format(**context, **html_tags_context)

View File

@@ -70,10 +70,12 @@ def get_user_preferences_for_courses(course_ids, user):
return new_preferences
def send_digest_email_to_user(user, cadence_type, course_language='en', courses_data=None):
def send_digest_email_to_user(user, cadence_type, start_date, end_date, course_language='en', courses_data=None):
"""
Send [cadence_type] email to user.
Cadence Type can be EmailCadence.DAILY or EmailCadence.WEEKLY
start_date: Datetime object
end_date: Datetime object
"""
if cadence_type not in [EmailCadence.DAILY, EmailCadence.WEEKLY]:
raise ValueError('Invalid cadence_type')
@@ -81,7 +83,6 @@ def send_digest_email_to_user(user, cadence_type, course_language='en', courses_
if not is_email_notification_flag_enabled(user):
logger.info(f'<Email Cadence> Flag disabled for {user.username} ==Temp Log==')
return
start_date, end_date = get_start_end_date(cadence_type)
notifications = Notification.objects.filter(user=user, email=True,
created__gte=start_date, created__lte=end_date)
if not notifications:
@@ -115,6 +116,7 @@ def send_digest_email_to_all_users(cadence_type):
logger.info(f'<Email Cadence> Sending cadence email of type {cadence_type}')
users = get_audience_for_cadence_email(cadence_type)
courses_data = {}
start_date, end_date = get_start_end_date(cadence_type)
logger.info(f'<Email Cadence> Email Cadence Audience {len(users)}')
for user in users:
send_digest_email_to_user(user, cadence_type, courses_data=courses_data)
send_digest_email_to_user(user, cadence_type, start_date, end_date, courses_data=courses_data)

View File

@@ -16,6 +16,7 @@ from openedx.core.djangoapps.notifications.email.tasks import (
send_digest_email_to_all_users,
send_digest_email_to_user
)
from openedx.core.djangoapps.notifications.email.utils import get_start_end_date
from openedx.core.djangoapps.notifications.models import CourseNotificationPreference
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@@ -42,8 +43,9 @@ class TestEmailDigestForUser(ModuleStoreTestCase):
"""
Tests email is sent iff waffle flag is enabled
"""
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
send_digest_email_to_user(self.user, EmailCadence.DAILY)
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
assert not mock_func.called
@ddt.data(True, False)
@@ -54,8 +56,9 @@ class TestEmailDigestForUser(ModuleStoreTestCase):
"""
created_date = datetime.datetime.now() - datetime.timedelta(days=1)
create_notification(self.user, self.course.id, created=created_date)
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, flag_value):
send_digest_email_to_user(self.user, EmailCadence.DAILY)
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
assert mock_func.called is flag_value
@patch('edx_ace.ace.send')
@@ -63,9 +66,10 @@ class TestEmailDigestForUser(ModuleStoreTestCase):
"""
Tests email is not sent if notification is created on next day
"""
create_notification(self.user, self.course.id)
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
create_notification(self.user, self.course.id, created=end_date + datetime.timedelta(minutes=2))
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
send_digest_email_to_user(self.user, EmailCadence.DAILY)
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
assert not mock_func.called
@patch('edx_ace.ace.send')
@@ -73,12 +77,35 @@ class TestEmailDigestForUser(ModuleStoreTestCase):
"""
Tests email is not sent if notification is created day before yesterday
"""
created_date = datetime.datetime.now() - datetime.timedelta(days=2)
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
created_date = datetime.datetime.now() - datetime.timedelta(days=1, minutes=18)
create_notification(self.user, self.course.id, created=created_date)
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
send_digest_email_to_user(self.user, EmailCadence.DAILY)
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
assert not mock_func.called
@ddt.data(
(EmailCadence.DAILY, datetime.datetime.now() - datetime.timedelta(days=1, minutes=30), False),
(EmailCadence.DAILY, datetime.datetime.now() - datetime.timedelta(minutes=10), True),
(EmailCadence.DAILY, datetime.datetime.now() - datetime.timedelta(days=1), True),
(EmailCadence.DAILY, datetime.datetime.now() + datetime.timedelta(minutes=20), False),
(EmailCadence.WEEKLY, datetime.datetime.now() - datetime.timedelta(days=7, minutes=30), False),
(EmailCadence.WEEKLY, datetime.datetime.now() - datetime.timedelta(days=7), True),
(EmailCadence.WEEKLY, datetime.datetime.now() - datetime.timedelta(minutes=20), True),
(EmailCadence.WEEKLY, datetime.datetime.now() + datetime.timedelta(minutes=20), False),
)
@ddt.unpack
@patch('edx_ace.ace.send')
def test_notification_content(self, cadence_type, created_time, notification_created, mock_func):
"""
Tests email only contains notification created within date
"""
start_date, end_date = get_start_end_date(cadence_type)
create_notification(self.user, self.course.id, created=created_time)
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
assert mock_func.called is notification_created
@ddt.ddt
class TestEmailDigestAudience(ModuleStoreTestCase):
@@ -146,10 +173,11 @@ class TestEmailDigestAudience(ModuleStoreTestCase):
"""
Tests email is sent only when notifications with email=True exists
"""
created_date = datetime.datetime.now() - datetime.timedelta(days=1)
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
created_date = datetime.datetime.now() - datetime.timedelta(hours=23, minutes=59)
create_notification(self.user, self.course.id, created=created_date, email=email_value)
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
send_digest_email_to_user(self.user, EmailCadence.DAILY)
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
assert mock_func.called is email_value
@@ -166,7 +194,7 @@ class TestPreferences(ModuleStoreTestCase):
self.user = UserFactory()
self.course = CourseFactory.create(display_name='test course', run="Testing_course")
self.preference = CourseNotificationPreference.objects.create(user=self.user, course_id=self.course.id)
created_date = datetime.datetime.now() - datetime.timedelta(days=1)
created_date = datetime.datetime.now() - datetime.timedelta(hours=23)
create_notification(self.user, self.course.id, notification_type='new_discussion_post', created=created_date)
@patch('edx_ace.ace.send')
@@ -174,13 +202,14 @@ class TestPreferences(ModuleStoreTestCase):
"""
Tests email is send for digest notification preference
"""
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
config = self.preference.notification_preference_config
types = config['discussion']['notification_types']
types['new_discussion_post']['email_cadence'] = EmailCadence.DAILY
types['new_discussion_post']['email'] = True
self.preference.save()
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
send_digest_email_to_user(self.user, EmailCadence.DAILY)
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
assert mock_func.called
@ddt.data(True, False)
@@ -189,13 +218,14 @@ class TestPreferences(ModuleStoreTestCase):
"""
Tests email is sent iff preference value is True
"""
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
config = self.preference.notification_preference_config
types = config['discussion']['notification_types']
types['new_discussion_post']['email_cadence'] = EmailCadence.DAILY
types['new_discussion_post']['email'] = pref_value
self.preference.save()
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
send_digest_email_to_user(self.user, EmailCadence.DAILY)
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
assert mock_func.called is pref_value
@patch('edx_ace.ace.send')
@@ -203,10 +233,11 @@ class TestPreferences(ModuleStoreTestCase):
"""
Tests email is not send if digest notification preference doesnot match
"""
start_date, end_date = get_start_end_date(EmailCadence.DAILY)
config = self.preference.notification_preference_config
types = config['discussion']['notification_types']
types['new_discussion_post']['email_cadence'] = EmailCadence.WEEKLY
self.preference.save()
with override_waffle_flag(ENABLE_EMAIL_NOTIFICATIONS, True):
send_digest_email_to_user(self.user, EmailCadence.DAILY)
send_digest_email_to_user(self.user, EmailCadence.DAILY, start_date, end_date)
assert not mock_func.called

View File

@@ -70,7 +70,7 @@ class TestUtilFunctions(ModuleStoreTestCase):
"""
Notification.objects.all().delete()
create_notification(self.user, self.course.id, app_name='discussion', notification_type='new_comment')
create_notification(self.user, self.course.id, app_name='updates', notification_type='course_update')
create_notification(self.user, self.course.id, app_name='updates', notification_type='course_updates')
app_dict = create_app_notifications_dict(Notification.objects.all())
assert len(app_dict.keys()) == 2
for key in ['discussion', 'updates']:
@@ -130,7 +130,7 @@ class TestContextFunctions(ModuleStoreTestCase):
discussion_notification = create_notification(self.user, self.course.id, app_name='discussion',
notification_type='new_comment')
update_notification = create_notification(self.user, self.course.id, app_name='updates',
notification_type='course_update')
notification_type='course_updates')
app_dict = create_app_notifications_dict(Notification.objects.all())
end_date = datetime.datetime(2024, 3, 24, 12, 0)
params = {

View File

@@ -7,7 +7,6 @@ import json
from django.conf import settings
from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
from django.urls import reverse
from pytz import utc
from waffle import get_waffle_flag_model # pylint: disable=invalid-django-waffle-import
@@ -20,7 +19,10 @@ from openedx.core.djangoapps.notifications.base_notification import (
)
from openedx.core.djangoapps.notifications.config.waffle import ENABLE_EMAIL_NOTIFICATIONS
from openedx.core.djangoapps.notifications.email_notifications import EmailCadence
from openedx.core.djangoapps.notifications.models import CourseNotificationPreference
from openedx.core.djangoapps.notifications.models import (
CourseNotificationPreference,
get_course_notification_preference_config_version
)
from xmodule.modulestore.django import modulestore
from .notification_icons import NotificationTypeIcons
@@ -71,15 +73,7 @@ def get_unsubscribe_link(username, patch):
"""
encrypted_username = encrypt_string(username)
encrypted_patch = encrypt_object(patch)
kwargs = {
'username': encrypted_username,
'patch': encrypted_patch
}
relative_url = reverse('preference_update_from_encrypted_username_view', kwargs=kwargs)
protocol = 'https://'
if settings.DEBUG:
protocol = 'http://'
return f"{protocol}{settings.LMS_BASE}{relative_url}"
return f"{settings.LEARNING_MICROFRONTEND_URL}/preferences-unsubscribe/{encrypted_username}/{encrypted_patch}"
def create_email_template_context(username):
@@ -171,13 +165,10 @@ def get_start_end_date(cadence_type):
"""
if cadence_type not in [EmailCadence.DAILY, EmailCadence.WEEKLY]:
raise ValueError('Invalid cadence_type')
date_today = datetime.datetime.now()
yesterday = date_today - datetime.timedelta(days=1)
end_date = datetime.datetime.combine(yesterday, datetime.time.max)
start_date = end_date
end_date = datetime.datetime.now()
start_date = end_date - datetime.timedelta(days=1, minutes=15)
if cadence_type == EmailCadence.WEEKLY:
start_date = end_date - datetime.timedelta(days=6)
start_date = datetime.datetime.combine(start_date, datetime.time.min)
start_date = start_date - datetime.timedelta(days=6)
return utc.localize(start_date), utc.localize(end_date)
@@ -366,6 +357,14 @@ def update_user_preferences_from_patch(encrypted_username, encrypted_patch):
return COURSE_NOTIFICATION_APPS[app_name]['core_email_cadence']
return COURSE_NOTIFICATION_TYPES[notification_type]['email_cadence']
def get_updated_preference(pref):
"""
Update preference if config version doesn't match
"""
if pref.config_version != get_course_notification_preference_config_version():
pref = pref.get_user_course_preference(pref.user_id, pref.course_id)
return pref
course_ids = CourseEnrollment.objects.filter(user=user).values_list('course_id', flat=True)
CourseNotificationPreference.objects.bulk_create(
[
@@ -378,6 +377,7 @@ def update_user_preferences_from_patch(encrypted_username, encrypted_patch):
# pylint: disable=too-many-nested-blocks
for preference in preferences:
preference = get_updated_preference(preference)
preference_json = preference.notification_preference_config
for app_name, app_prefs in preference_json.items():
if not is_name_match(app_name, app_value):

View File

@@ -23,7 +23,7 @@ NOTIFICATION_CHANNELS = ['web', 'push', 'email']
ADDITIONAL_NOTIFICATION_CHANNEL_SETTINGS = ['email_cadence']
# Update this version when there is a change to any course specific notification type or app.
COURSE_NOTIFICATION_CONFIG_VERSION = 10
COURSE_NOTIFICATION_CONFIG_VERSION = 11
def get_course_notification_preference_config():

View File

@@ -0,0 +1,35 @@
"""
Helper functions for overriding notification content for given notification type.
"""
def get_notification_type_content_function(notification_type):
"""
Returns the content function for the given notification if it exists.
"""
try:
return globals()[f"get_{notification_type}_notification_content"]
except KeyError:
return None
def get_notification_content_with_author_pronoun(notification_type, context):
"""
Helper function to get notification content with author's pronoun.
"""
html_tags_context = {
'strong': 'strong',
'p': 'p',
}
notification_type_content_template = notification_type.get('content_template', None)
if 'author_pronoun' in context:
context['author_name'] = context['author_pronoun']
if notification_type_content_template:
return notification_type_content_template.format(**context, **html_tags_context)
return ''
# Returns notification content for the new_comment notification.
get_new_comment_notification_content = get_notification_content_with_author_pronoun
# Returns notification content for the comment_on_followed_post notification.
get_comment_on_followed_post_notification_content = get_notification_content_with_author_pronoun

View File

@@ -33,7 +33,7 @@
</td>
<td class="notification-content" width="100%" align="left" valign="top" style=" padding: 1rem 1rem 1rem 0.5rem">
<p style="font-size: 0.875rem; font-weight:400; line-height:24px; color:#454545; margin: 0;">
{{ notification.content | safe }}
{{ notification.content | truncatechars_html:600 | safe }}
</p>
<p style="height: 0.5rem; margin: 0"></p>
<p style="color:#707070; margin: 0">

View File

@@ -38,7 +38,7 @@
Notification Settings
</a>
<a href="{{unsubscribe_url}}" rel="noopener noreferrer" target="_blank" style="color: black; margin-left: 1rem">
Unsubscribe
Unsubscribe from email digest for learning activity
</a>
</p>
<p>

View File

@@ -5,6 +5,13 @@
style="background: #00262b; color: white; width: 100%; padding: 1.5rem"
>
<tbody>
<tr align="right">
<td>
<a href="{{unsubscribe_url}}" rel="noopener noreferrer" target="_blank" style="color: white; text-decoration: none; font-size: 12px; line-height: 10px">
Unsubscribe
</a>
</td>
</tr>
<tr align="center">
<td>
<img src="{{ logo_url }}" style="width: 64px" height="auto" alt="logo_url" />

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ _("Email Digest Preferences Updated") }}</title>
</head>
<body>
<script>
alert('{{ _("You have successfully unsubscribed from email digest for learning activity") }}');
window.location.replace("{{ notification_preferences_url }}");
</script>
</body>
</html>

View File

@@ -390,7 +390,7 @@ class TestDeleteNotificationTask(ModuleStoreTestCase):
"""
assert not Notification.objects.all()
create_notification(self.user, self.course_1.id, app_name='discussion', notification_type='new_comment')
create_notification(self.user, self.course_1.id, app_name='updates', notification_type='course_update')
create_notification(self.user, self.course_1.id, app_name='updates', notification_type='course_updates')
delete_notifications({'app_name': 'discussion'})
assert not Notification.objects.filter(app_name='discussion')
assert Notification.objects.filter(app_name='updates')

View File

@@ -28,9 +28,13 @@ from openedx.core.djangoapps.django_comment_common.models import (
)
from openedx.core.djangoapps.notifications.config.waffle import ENABLE_NOTIFICATIONS
from openedx.core.djangoapps.notifications.email_notifications import EmailCadence
from openedx.core.djangoapps.notifications.models import CourseNotificationPreference, Notification
from openedx.core.djangoapps.notifications.models import (
CourseNotificationPreference,
Notification,
get_course_notification_preference_config_version
)
from openedx.core.djangoapps.notifications.serializers import NotificationCourseEnrollmentSerializer
from openedx.core.djangoapps.notifications.email.utils import get_unsubscribe_link
from openedx.core.djangoapps.notifications.email.utils import encrypt_object, encrypt_string
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@@ -275,9 +279,9 @@ class UserNotificationPreferenceAPITest(ModuleStoreTestCase):
'enabled': True,
'core_notification_types': [],
'notification_types': {
'course_update': {
'course_updates': {
'web': True,
'email': True,
'email': False,
'push': True,
'email_cadence': 'Daily',
'info': ''
@@ -910,7 +914,13 @@ class UpdatePreferenceFromEncryptedDataView(ModuleStoreTestCase):
"""
Tests if preference is updated when url is hit
"""
url = get_unsubscribe_link(self.user.username, {'channel': 'email', 'value': False})
user_hash = encrypt_string(self.user.username)
patch_hash = encrypt_object({'channel': 'email', 'value': False})
url_params = {
"username": user_hash,
"patch": patch_hash
}
url = reverse("preference_update_from_encrypted_username_view", kwargs=url_params)
func = getattr(self.client, request_type)
response = func(url)
assert response.status_code == status.HTTP_200_OK
@@ -921,6 +931,24 @@ class UpdatePreferenceFromEncryptedDataView(ModuleStoreTestCase):
assert type_prefs['email'] is False
assert type_prefs['email_cadence'] == EmailCadence.NEVER
def test_if_config_version_is_updated(self):
"""
Tests if preference version is updated before applying patch data
"""
preference = CourseNotificationPreference.objects.get(user=self.user, course_id=self.course.id)
preference.config_version -= 1
preference.save()
user_hash = encrypt_string(self.user.username)
patch_hash = encrypt_object({'channel': 'email', 'value': False})
url_params = {
"username": user_hash,
"patch": patch_hash
}
url = reverse("preference_update_from_encrypted_username_view", kwargs=url_params)
self.client.get(url)
preference = CourseNotificationPreference.objects.get(user=self.user, course_id=self.course.id)
assert preference.config_version == get_course_notification_preference_config_version()
def remove_notifications_with_visibility_settings(expected_response):
"""

View File

@@ -5,7 +5,6 @@ from datetime import datetime, timedelta
from django.conf import settings
from django.db.models import Count
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as _
from opaque_keys.edx.keys import CourseKey
@@ -442,4 +441,4 @@ def preference_update_from_encrypted_username_view(request, username, patch):
username and patch must be string
"""
update_user_preferences_from_patch(username, patch)
return HttpResponse("<!DOCTYPE html><html><body>Success</body></html>", status=status.HTTP_200_OK)
return Response({"result": "success"}, status=status.HTTP_200_OK)

View File

@@ -257,7 +257,7 @@ class BlockFieldsView(APIView):
# Signal that we've modified this block
context_impl = get_learning_context_impl(usage_key)
context_impl.send_updated_event(usage_key)
context_impl.send_block_updated_event(usage_key)
return Response({
"id": str(block.location),

1511
package-lock.json generated
View File

@@ -9,11 +9,11 @@
"version": "0.1.0",
"hasInstallScript": true,
"dependencies": {
"@babel/core": "7.19.0",
"@babel/core": "7.25.2",
"@babel/plugin-proposal-object-rest-spread": "^7.18.9",
"@babel/plugin-transform-object-assign": "^7.18.6",
"@babel/preset-env": "^7.19.0",
"@babel/preset-react": "7.18.6",
"@babel/preset-react": "7.24.7",
"@edx/brand-edx.org": "^2.0.7",
"@edx/edx-bootstrap": "1.0.4",
"@edx/edx-proctoring": "^4.18.1",
@@ -23,7 +23,7 @@
"babel-loader": "^9.1.3",
"babel-plugin-transform-class-properties": "6.24.1",
"babel-polyfill": "6.26.0",
"backbone": "1.4.1",
"backbone": "1.6.0",
"backbone-associations": "0.6.2",
"backbone.paginator": "2.0.8",
"bootstrap": "4.0.0",
@@ -59,7 +59,7 @@
"react-slick": "0.29.0",
"redux": "3.7.2",
"redux-thunk": "2.2.0",
"requirejs": "2.3.6",
"requirejs": "2.3.7",
"rtlcss": "2.6.2",
"sass": "^1.54.8",
"sass-loader": "^14.1.1",
@@ -79,29 +79,29 @@
"@edx/eslint-config": "^3.1.1",
"@edx/mockprock": "github:openedx/mockprock#3ad18c6888e6521e9bf7a4df0db6f8579b928235",
"@edx/stylelint-config-edx": "2.3.3",
"babel-jest": "26.0.0",
"babel-jest": "26.6.3",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.8",
"eslint-import-resolver-webpack": "0.13.8",
"jasmine-core": "2.6.4",
"jasmine-jquery": "git+https://git@github.com/velesin/jasmine-jquery.git#ebad463d592d3fea00c69f26ea18a930e09c7b58",
"jest": "26.0.0",
"jest-enzyme": "6.0.2",
"jest": "26.6.3",
"jest-enzyme": "6.1.2",
"karma": "0.13.22",
"karma-chrome-launcher": "0.2.3",
"karma-coverage": "0.5.5",
"karma-firefox-launcher": "0.1.7",
"karma-jasmine": "0.3.8",
"karma-jasmine-html-reporter": "0.2.2",
"karma-junit-reporter": "1.1.0",
"karma-junit-reporter": "1.2.0",
"karma-requirejs": "0.2.6",
"karma-selenium-webdriver-launcher": "github:openedx/karma-selenium-webdriver-launcher#0.0.4-openedx.0",
"karma-sourcemap-loader": "0.3.7",
"karma-sourcemap-loader": "0.4.0",
"karma-spec-reporter": "0.0.36",
"karma-webpack": "^5.0.1",
"plato": "1.7.0",
"react-test-renderer": "16.4.0",
"selenium-webdriver": "3.4.0",
"react-test-renderer": "16.14.0",
"selenium-webdriver": "3.6.0",
"sinon": "2.4.1",
"squirejs": "0.1.0",
"string-replace-loader": "^3.1.0",
@@ -132,11 +132,12 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.24.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
"integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
"integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
"license": "MIT",
"dependencies": {
"@babel/highlight": "^7.24.2",
"@babel/highlight": "^7.24.7",
"picocolors": "^1.0.0"
},
"engines": {
@@ -144,33 +145,35 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz",
"integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==",
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz",
"integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.0.tgz",
"integrity": "sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==",
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz",
"integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==",
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.19.0",
"@babel/helper-compilation-targets": "^7.19.0",
"@babel/helper-module-transforms": "^7.19.0",
"@babel/helpers": "^7.19.0",
"@babel/parser": "^7.19.0",
"@babel/template": "^7.18.10",
"@babel/traverse": "^7.19.0",
"@babel/types": "^7.19.0",
"convert-source-map": "^1.7.0",
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.25.0",
"@babel/helper-compilation-targets": "^7.25.2",
"@babel/helper-module-transforms": "^7.25.2",
"@babel/helpers": "^7.25.0",
"@babel/parser": "^7.25.0",
"@babel/template": "^7.25.0",
"@babel/traverse": "^7.25.2",
"@babel/types": "^7.25.2",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.2.1",
"semver": "^6.3.0"
"json5": "^2.2.3",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
@@ -180,12 +183,19 @@
"url": "https://opencollective.com/babel"
}
},
"node_modules/@babel/core/node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"license": "MIT"
},
"node_modules/@babel/generator": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz",
"integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==",
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz",
"integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.0",
"@babel/types": "^7.25.0",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^2.5.1"
@@ -195,35 +205,39 @@
}
},
"node_modules/@babel/helper-annotate-as-pure": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
"integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz",
"integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.22.5"
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-builder-binary-assignment-operator-visitor": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz",
"integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz",
"integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.22.15"
"@babel/traverse": "^7.24.7",
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz",
"integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==",
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz",
"integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==",
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.23.5",
"@babel/helper-validator-option": "^7.23.5",
"browserslist": "^4.22.2",
"@babel/compat-data": "^7.25.2",
"@babel/helper-validator-option": "^7.24.8",
"browserslist": "^4.23.1",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
},
@@ -232,18 +246,17 @@
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz",
"integrity": "sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA==",
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz",
"integrity": "sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-member-expression-to-functions": "^7.23.0",
"@babel/helper-optimise-call-expression": "^7.22.5",
"@babel/helper-replace-supers": "^7.24.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/helper-annotate-as-pure": "^7.24.7",
"@babel/helper-member-expression-to-functions": "^7.24.8",
"@babel/helper-optimise-call-expression": "^7.24.7",
"@babel/helper-replace-supers": "^7.25.0",
"@babel/helper-skip-transparent-expression-wrappers": "^7.24.7",
"@babel/traverse": "^7.25.0",
"semver": "^6.3.1"
},
"engines": {
@@ -254,11 +267,12 @@
}
},
"node_modules/@babel/helper-create-regexp-features-plugin": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz",
"integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==",
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz",
"integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-annotate-as-pure": "^7.24.7",
"regexpu-core": "^5.3.1",
"semver": "^6.3.1"
},
@@ -284,69 +298,42 @@
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/@babel/helper-environment-visitor": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-function-name": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
"dependencies": {
"@babel/template": "^7.22.15",
"@babel/types": "^7.23.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-hoist-variables": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"dependencies": {
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-member-expression-to-functions": {
"version": "7.23.0",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz",
"integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==",
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz",
"integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.23.0"
"@babel/traverse": "^7.24.8",
"@babel/types": "^7.24.8"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports": {
"version": "7.24.3",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz",
"integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
"integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.0"
"@babel/traverse": "^7.24.7",
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
"integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz",
"integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-module-imports": "^7.22.15",
"@babel/helper-simple-access": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/helper-validator-identifier": "^7.22.20"
"@babel/helper-module-imports": "^7.24.7",
"@babel/helper-simple-access": "^7.24.7",
"@babel/helper-validator-identifier": "^7.24.7",
"@babel/traverse": "^7.25.2"
},
"engines": {
"node": ">=6.9.0"
@@ -356,32 +343,35 @@
}
},
"node_modules/@babel/helper-optimise-call-expression": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz",
"integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz",
"integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.22.5"
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz",
"integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==",
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz",
"integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-remap-async-to-generator": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz",
"integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==",
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz",
"integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-wrap-function": "^7.22.20"
"@babel/helper-annotate-as-pure": "^7.24.7",
"@babel/helper-wrap-function": "^7.25.0",
"@babel/traverse": "^7.25.0"
},
"engines": {
"node": ">=6.9.0"
@@ -391,13 +381,14 @@
}
},
"node_modules/@babel/helper-replace-supers": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz",
"integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==",
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz",
"integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==",
"license": "MIT",
"dependencies": {
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-member-expression-to-functions": "^7.23.0",
"@babel/helper-optimise-call-expression": "^7.22.5"
"@babel/helper-member-expression-to-functions": "^7.24.8",
"@babel/helper-optimise-call-expression": "^7.24.7",
"@babel/traverse": "^7.25.0"
},
"engines": {
"node": ">=6.9.0"
@@ -407,94 +398,92 @@
}
},
"node_modules/@babel/helper-simple-access": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
"integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz",
"integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.22.5"
"@babel/traverse": "^7.24.7",
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-skip-transparent-expression-wrappers": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz",
"integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz",
"integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-split-export-declaration": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
"dependencies": {
"@babel/types": "^7.22.5"
"@babel/traverse": "^7.24.7",
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
"integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==",
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
"integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==",
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz",
"integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-wrap-function": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz",
"integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==",
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz",
"integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-function-name": "^7.22.5",
"@babel/template": "^7.22.15",
"@babel/types": "^7.22.19"
"@babel/template": "^7.25.0",
"@babel/traverse": "^7.25.0",
"@babel/types": "^7.25.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz",
"integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==",
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz",
"integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.24.0",
"@babel/traverse": "^7.24.1",
"@babel/types": "^7.24.0"
"@babel/template": "^7.25.0",
"@babel/types": "^7.25.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
"version": "7.24.2",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz",
"integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
"integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
"@babel/helper-validator-identifier": "^7.24.7",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
@@ -504,9 +493,13 @@
}
},
"node_modules/@babel/parser": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz",
"integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==",
"version": "7.25.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
"integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.25.2"
},
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -514,12 +507,44 @@
"node": ">=6.0.0"
}
},
"node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz",
"integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==",
"node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
"version": "7.25.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz",
"integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.8",
"@babel/traverse": "^7.25.3"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": {
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz",
"integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.8"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz",
"integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.8"
},
"engines": {
"node": ">=6.9.0"
@@ -529,13 +554,14 @@
}
},
"node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz",
"integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz",
"integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
"@babel/plugin-transform-optional-chaining": "^7.24.1"
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/helper-skip-transparent-expression-wrappers": "^7.24.7",
"@babel/plugin-transform-optional-chaining": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -545,12 +571,13 @@
}
},
"node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz",
"integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==",
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz",
"integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==",
"license": "MIT",
"dependencies": {
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.8",
"@babel/traverse": "^7.25.0"
},
"engines": {
"node": ">=6.9.0"
@@ -660,11 +687,12 @@
}
},
"node_modules/@babel/plugin-syntax-import-assertions": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz",
"integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz",
"integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -674,11 +702,12 @@
}
},
"node_modules/@babel/plugin-syntax-import-attributes": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz",
"integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz",
"integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -710,11 +739,12 @@
}
},
"node_modules/@babel/plugin-syntax-jsx": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz",
"integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz",
"integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -833,11 +863,12 @@
}
},
"node_modules/@babel/plugin-transform-arrow-functions": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz",
"integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz",
"integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -847,14 +878,15 @@
}
},
"node_modules/@babel/plugin-transform-async-generator-functions": {
"version": "7.24.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz",
"integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==",
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz",
"integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==",
"license": "MIT",
"dependencies": {
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-remap-async-to-generator": "^7.22.20",
"@babel/plugin-syntax-async-generators": "^7.8.4"
"@babel/helper-plugin-utils": "^7.24.8",
"@babel/helper-remap-async-to-generator": "^7.25.0",
"@babel/plugin-syntax-async-generators": "^7.8.4",
"@babel/traverse": "^7.25.0"
},
"engines": {
"node": ">=6.9.0"
@@ -864,13 +896,14 @@
}
},
"node_modules/@babel/plugin-transform-async-to-generator": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz",
"integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz",
"integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.24.1",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-remap-async-to-generator": "^7.22.20"
"@babel/helper-module-imports": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/helper-remap-async-to-generator": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -880,11 +913,12 @@
}
},
"node_modules/@babel/plugin-transform-block-scoped-functions": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz",
"integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz",
"integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -894,11 +928,12 @@
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz",
"integrity": "sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==",
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz",
"integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.8"
},
"engines": {
"node": ">=6.9.0"
@@ -908,12 +943,13 @@
}
},
"node_modules/@babel/plugin-transform-class-properties": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz",
"integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz",
"integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.24.1",
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-create-class-features-plugin": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -923,12 +959,13 @@
}
},
"node_modules/@babel/plugin-transform-class-static-block": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.1.tgz",
"integrity": "sha512-FUHlKCn6J3ERiu8Dv+4eoz7w8+kFLSyeVG4vDAikwADGjUCoHw/JHokyGtr8OR4UjpwPVivyF+h8Q5iv/JmrtA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz",
"integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.24.1",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-create-class-features-plugin": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/plugin-syntax-class-static-block": "^7.14.5"
},
"engines": {
@@ -939,17 +976,16 @@
}
},
"node_modules/@babel/plugin-transform-classes": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz",
"integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==",
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.0.tgz",
"integrity": "sha512-xyi6qjr/fYU304fiRwFbekzkqVJZ6A7hOjWZd+89FVcBqPV3S9Wuozz82xdpLspckeaafntbzglaW4pqpzvtSw==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-replace-supers": "^7.24.1",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/helper-annotate-as-pure": "^7.24.7",
"@babel/helper-compilation-targets": "^7.24.8",
"@babel/helper-plugin-utils": "^7.24.8",
"@babel/helper-replace-supers": "^7.25.0",
"@babel/traverse": "^7.25.0",
"globals": "^11.1.0"
},
"engines": {
@@ -960,12 +996,13 @@
}
},
"node_modules/@babel/plugin-transform-computed-properties": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz",
"integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz",
"integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/template": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/template": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -975,11 +1012,12 @@
}
},
"node_modules/@babel/plugin-transform-destructuring": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz",
"integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==",
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz",
"integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.8"
},
"engines": {
"node": ">=6.9.0"
@@ -989,12 +1027,13 @@
}
},
"node_modules/@babel/plugin-transform-dotall-regex": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz",
"integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz",
"integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.15",
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-create-regexp-features-plugin": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1004,11 +1043,12 @@
}
},
"node_modules/@babel/plugin-transform-duplicate-keys": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz",
"integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz",
"integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1017,12 +1057,29 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-dynamic-import": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz",
"integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==",
"node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": {
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz",
"integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-create-regexp-features-plugin": "^7.25.0",
"@babel/helper-plugin-utils": "^7.24.8"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/plugin-transform-dynamic-import": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz",
"integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/plugin-syntax-dynamic-import": "^7.8.3"
},
"engines": {
@@ -1033,12 +1090,13 @@
}
},
"node_modules/@babel/plugin-transform-exponentiation-operator": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz",
"integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz",
"integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15",
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1048,11 +1106,12 @@
}
},
"node_modules/@babel/plugin-transform-export-namespace-from": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz",
"integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz",
"integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/plugin-syntax-export-namespace-from": "^7.8.3"
},
"engines": {
@@ -1063,12 +1122,13 @@
}
},
"node_modules/@babel/plugin-transform-for-of": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz",
"integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz",
"integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5"
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/helper-skip-transparent-expression-wrappers": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1078,13 +1138,14 @@
}
},
"node_modules/@babel/plugin-transform-function-name": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz",
"integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==",
"version": "7.25.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz",
"integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==",
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-compilation-targets": "^7.24.8",
"@babel/helper-plugin-utils": "^7.24.8",
"@babel/traverse": "^7.25.1"
},
"engines": {
"node": ">=6.9.0"
@@ -1094,11 +1155,12 @@
}
},
"node_modules/@babel/plugin-transform-json-strings": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz",
"integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz",
"integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/plugin-syntax-json-strings": "^7.8.3"
},
"engines": {
@@ -1109,11 +1171,12 @@
}
},
"node_modules/@babel/plugin-transform-literals": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz",
"integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==",
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz",
"integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.8"
},
"engines": {
"node": ">=6.9.0"
@@ -1123,11 +1186,12 @@
}
},
"node_modules/@babel/plugin-transform-logical-assignment-operators": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz",
"integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz",
"integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
},
"engines": {
@@ -1138,11 +1202,12 @@
}
},
"node_modules/@babel/plugin-transform-member-expression-literals": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz",
"integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz",
"integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1152,12 +1217,13 @@
}
},
"node_modules/@babel/plugin-transform-modules-amd": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz",
"integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz",
"integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.23.3",
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-module-transforms": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1167,13 +1233,14 @@
}
},
"node_modules/@babel/plugin-transform-modules-commonjs": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz",
"integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==",
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz",
"integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.23.3",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-simple-access": "^7.22.5"
"@babel/helper-module-transforms": "^7.24.8",
"@babel/helper-plugin-utils": "^7.24.8",
"@babel/helper-simple-access": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1183,14 +1250,15 @@
}
},
"node_modules/@babel/plugin-transform-modules-systemjs": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz",
"integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==",
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz",
"integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==",
"license": "MIT",
"dependencies": {
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-module-transforms": "^7.23.3",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-validator-identifier": "^7.22.20"
"@babel/helper-module-transforms": "^7.25.0",
"@babel/helper-plugin-utils": "^7.24.8",
"@babel/helper-validator-identifier": "^7.24.7",
"@babel/traverse": "^7.25.0"
},
"engines": {
"node": ">=6.9.0"
@@ -1200,12 +1268,13 @@
}
},
"node_modules/@babel/plugin-transform-modules-umd": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz",
"integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz",
"integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.23.3",
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-module-transforms": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1215,12 +1284,13 @@
}
},
"node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz",
"integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz",
"integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
"@babel/helper-plugin-utils": "^7.22.5"
"@babel/helper-create-regexp-features-plugin": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1230,11 +1300,12 @@
}
},
"node_modules/@babel/plugin-transform-new-target": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz",
"integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz",
"integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1244,11 +1315,12 @@
}
},
"node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz",
"integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz",
"integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
},
"engines": {
@@ -1259,11 +1331,12 @@
}
},
"node_modules/@babel/plugin-transform-numeric-separator": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz",
"integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz",
"integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/plugin-syntax-numeric-separator": "^7.10.4"
},
"engines": {
@@ -1274,11 +1347,12 @@
}
},
"node_modules/@babel/plugin-transform-object-assign": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.24.1.tgz",
"integrity": "sha512-I1kctor9iKtupb7jv7FyjApHCuKLBKCblVAeHVK9PB6FW7GI0ac6RtobC3MwwJy8CZ1JxuhQmnbrsqI5G8hAIg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.24.7.tgz",
"integrity": "sha512-DOzAi77P9jSyPijHS7Z8vH0wLRcZH6wWxuIZgLAiy8FWOkcKMJmnyHjy2JM94k6A0QxlA/hlLh+R9T3GEryjNQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1288,14 +1362,15 @@
}
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz",
"integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz",
"integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==",
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-compilation-targets": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
"@babel/plugin-transform-parameters": "^7.24.1"
"@babel/plugin-transform-parameters": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1305,12 +1380,13 @@
}
},
"node_modules/@babel/plugin-transform-object-super": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz",
"integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz",
"integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-replace-supers": "^7.24.1"
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/helper-replace-supers": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1320,11 +1396,12 @@
}
},
"node_modules/@babel/plugin-transform-optional-catch-binding": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz",
"integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz",
"integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
},
"engines": {
@@ -1335,12 +1412,13 @@
}
},
"node_modules/@babel/plugin-transform-optional-chaining": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz",
"integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==",
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz",
"integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
"@babel/helper-plugin-utils": "^7.24.8",
"@babel/helper-skip-transparent-expression-wrappers": "^7.24.7",
"@babel/plugin-syntax-optional-chaining": "^7.8.3"
},
"engines": {
@@ -1351,11 +1429,12 @@
}
},
"node_modules/@babel/plugin-transform-parameters": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz",
"integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz",
"integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1365,12 +1444,13 @@
}
},
"node_modules/@babel/plugin-transform-private-methods": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz",
"integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz",
"integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.24.1",
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-create-class-features-plugin": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1380,13 +1460,14 @@
}
},
"node_modules/@babel/plugin-transform-private-property-in-object": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz",
"integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz",
"integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-create-class-features-plugin": "^7.24.1",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-annotate-as-pure": "^7.24.7",
"@babel/helper-create-class-features-plugin": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/plugin-syntax-private-property-in-object": "^7.14.5"
},
"engines": {
@@ -1397,11 +1478,12 @@
}
},
"node_modules/@babel/plugin-transform-property-literals": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz",
"integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz",
"integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1411,11 +1493,12 @@
}
},
"node_modules/@babel/plugin-transform-react-display-name": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz",
"integrity": "sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz",
"integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1425,15 +1508,16 @@
}
},
"node_modules/@babel/plugin-transform-react-jsx": {
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz",
"integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==",
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz",
"integrity": "sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-module-imports": "^7.22.15",
"@babel/helper-plugin-utils": "^7.22.5",
"@babel/plugin-syntax-jsx": "^7.23.3",
"@babel/types": "^7.23.4"
"@babel/helper-annotate-as-pure": "^7.24.7",
"@babel/helper-module-imports": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.8",
"@babel/plugin-syntax-jsx": "^7.24.7",
"@babel/types": "^7.25.2"
},
"engines": {
"node": ">=6.9.0"
@@ -1443,11 +1527,12 @@
}
},
"node_modules/@babel/plugin-transform-react-jsx-development": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz",
"integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.24.7.tgz",
"integrity": "sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==",
"license": "MIT",
"dependencies": {
"@babel/plugin-transform-react-jsx": "^7.22.5"
"@babel/plugin-transform-react-jsx": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1457,12 +1542,13 @@
}
},
"node_modules/@babel/plugin-transform-react-pure-annotations": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.1.tgz",
"integrity": "sha512-+pWEAaDJvSm9aFvJNpLiM2+ktl2Sn2U5DdyiWdZBxmLc6+xGt88dvFqsHiAiDS+8WqUwbDfkKz9jRxK3M0k+kA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.7.tgz",
"integrity": "sha512-PLgBVk3fzbmEjBJ/u8kFzOqS9tUeDjiaWud/rRym/yjCo/M9cASPlnrd2ZmmZpQT40fOOrvR8jh+n8jikrOhNA==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-annotate-as-pure": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1472,11 +1558,12 @@
}
},
"node_modules/@babel/plugin-transform-regenerator": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz",
"integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz",
"integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-plugin-utils": "^7.24.7",
"regenerator-transform": "^0.15.2"
},
"engines": {
@@ -1487,11 +1574,12 @@
}
},
"node_modules/@babel/plugin-transform-reserved-words": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz",
"integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz",
"integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1501,11 +1589,12 @@
}
},
"node_modules/@babel/plugin-transform-shorthand-properties": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz",
"integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz",
"integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1515,12 +1604,13 @@
}
},
"node_modules/@babel/plugin-transform-spread": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz",
"integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz",
"integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5"
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/helper-skip-transparent-expression-wrappers": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1530,11 +1620,12 @@
}
},
"node_modules/@babel/plugin-transform-sticky-regex": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz",
"integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz",
"integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1544,11 +1635,12 @@
}
},
"node_modules/@babel/plugin-transform-template-literals": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz",
"integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz",
"integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1558,11 +1650,12 @@
}
},
"node_modules/@babel/plugin-transform-typeof-symbol": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz",
"integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==",
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz",
"integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.8"
},
"engines": {
"node": ">=6.9.0"
@@ -1572,11 +1665,12 @@
}
},
"node_modules/@babel/plugin-transform-unicode-escapes": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz",
"integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz",
"integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1586,12 +1680,13 @@
}
},
"node_modules/@babel/plugin-transform-unicode-property-regex": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz",
"integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz",
"integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.15",
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-create-regexp-features-plugin": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1601,12 +1696,13 @@
}
},
"node_modules/@babel/plugin-transform-unicode-regex": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz",
"integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz",
"integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.15",
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-create-regexp-features-plugin": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1616,12 +1712,13 @@
}
},
"node_modules/@babel/plugin-transform-unicode-sets-regex": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz",
"integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz",
"integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.15",
"@babel/helper-plugin-utils": "^7.24.0"
"@babel/helper-create-regexp-features-plugin": "^7.24.7",
"@babel/helper-plugin-utils": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1641,25 +1738,28 @@
}
},
"node_modules/@babel/preset-env": {
"version": "7.24.3",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.3.tgz",
"integrity": "sha512-fSk430k5c2ff8536JcPvPWK4tZDwehWLGlBp0wrsBUjZVdeQV6lePbwKWZaZfK2vnh/1kQX1PzAJWsnBmVgGJA==",
"version": "7.25.3",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz",
"integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==",
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.24.1",
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-validator-option": "^7.23.5",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1",
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1",
"@babel/compat-data": "^7.25.2",
"@babel/helper-compilation-targets": "^7.25.2",
"@babel/helper-plugin-utils": "^7.24.8",
"@babel/helper-validator-option": "^7.24.8",
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3",
"@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7",
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0",
"@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
"@babel/plugin-syntax-async-generators": "^7.8.4",
"@babel/plugin-syntax-class-properties": "^7.12.13",
"@babel/plugin-syntax-class-static-block": "^7.14.5",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-export-namespace-from": "^7.8.3",
"@babel/plugin-syntax-import-assertions": "^7.24.1",
"@babel/plugin-syntax-import-attributes": "^7.24.1",
"@babel/plugin-syntax-import-assertions": "^7.24.7",
"@babel/plugin-syntax-import-attributes": "^7.24.7",
"@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/plugin-syntax-json-strings": "^7.8.3",
"@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
@@ -1671,59 +1771,60 @@
"@babel/plugin-syntax-private-property-in-object": "^7.14.5",
"@babel/plugin-syntax-top-level-await": "^7.14.5",
"@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
"@babel/plugin-transform-arrow-functions": "^7.24.1",
"@babel/plugin-transform-async-generator-functions": "^7.24.3",
"@babel/plugin-transform-async-to-generator": "^7.24.1",
"@babel/plugin-transform-block-scoped-functions": "^7.24.1",
"@babel/plugin-transform-block-scoping": "^7.24.1",
"@babel/plugin-transform-class-properties": "^7.24.1",
"@babel/plugin-transform-class-static-block": "^7.24.1",
"@babel/plugin-transform-classes": "^7.24.1",
"@babel/plugin-transform-computed-properties": "^7.24.1",
"@babel/plugin-transform-destructuring": "^7.24.1",
"@babel/plugin-transform-dotall-regex": "^7.24.1",
"@babel/plugin-transform-duplicate-keys": "^7.24.1",
"@babel/plugin-transform-dynamic-import": "^7.24.1",
"@babel/plugin-transform-exponentiation-operator": "^7.24.1",
"@babel/plugin-transform-export-namespace-from": "^7.24.1",
"@babel/plugin-transform-for-of": "^7.24.1",
"@babel/plugin-transform-function-name": "^7.24.1",
"@babel/plugin-transform-json-strings": "^7.24.1",
"@babel/plugin-transform-literals": "^7.24.1",
"@babel/plugin-transform-logical-assignment-operators": "^7.24.1",
"@babel/plugin-transform-member-expression-literals": "^7.24.1",
"@babel/plugin-transform-modules-amd": "^7.24.1",
"@babel/plugin-transform-modules-commonjs": "^7.24.1",
"@babel/plugin-transform-modules-systemjs": "^7.24.1",
"@babel/plugin-transform-modules-umd": "^7.24.1",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5",
"@babel/plugin-transform-new-target": "^7.24.1",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1",
"@babel/plugin-transform-numeric-separator": "^7.24.1",
"@babel/plugin-transform-object-rest-spread": "^7.24.1",
"@babel/plugin-transform-object-super": "^7.24.1",
"@babel/plugin-transform-optional-catch-binding": "^7.24.1",
"@babel/plugin-transform-optional-chaining": "^7.24.1",
"@babel/plugin-transform-parameters": "^7.24.1",
"@babel/plugin-transform-private-methods": "^7.24.1",
"@babel/plugin-transform-private-property-in-object": "^7.24.1",
"@babel/plugin-transform-property-literals": "^7.24.1",
"@babel/plugin-transform-regenerator": "^7.24.1",
"@babel/plugin-transform-reserved-words": "^7.24.1",
"@babel/plugin-transform-shorthand-properties": "^7.24.1",
"@babel/plugin-transform-spread": "^7.24.1",
"@babel/plugin-transform-sticky-regex": "^7.24.1",
"@babel/plugin-transform-template-literals": "^7.24.1",
"@babel/plugin-transform-typeof-symbol": "^7.24.1",
"@babel/plugin-transform-unicode-escapes": "^7.24.1",
"@babel/plugin-transform-unicode-property-regex": "^7.24.1",
"@babel/plugin-transform-unicode-regex": "^7.24.1",
"@babel/plugin-transform-unicode-sets-regex": "^7.24.1",
"@babel/plugin-transform-arrow-functions": "^7.24.7",
"@babel/plugin-transform-async-generator-functions": "^7.25.0",
"@babel/plugin-transform-async-to-generator": "^7.24.7",
"@babel/plugin-transform-block-scoped-functions": "^7.24.7",
"@babel/plugin-transform-block-scoping": "^7.25.0",
"@babel/plugin-transform-class-properties": "^7.24.7",
"@babel/plugin-transform-class-static-block": "^7.24.7",
"@babel/plugin-transform-classes": "^7.25.0",
"@babel/plugin-transform-computed-properties": "^7.24.7",
"@babel/plugin-transform-destructuring": "^7.24.8",
"@babel/plugin-transform-dotall-regex": "^7.24.7",
"@babel/plugin-transform-duplicate-keys": "^7.24.7",
"@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0",
"@babel/plugin-transform-dynamic-import": "^7.24.7",
"@babel/plugin-transform-exponentiation-operator": "^7.24.7",
"@babel/plugin-transform-export-namespace-from": "^7.24.7",
"@babel/plugin-transform-for-of": "^7.24.7",
"@babel/plugin-transform-function-name": "^7.25.1",
"@babel/plugin-transform-json-strings": "^7.24.7",
"@babel/plugin-transform-literals": "^7.25.2",
"@babel/plugin-transform-logical-assignment-operators": "^7.24.7",
"@babel/plugin-transform-member-expression-literals": "^7.24.7",
"@babel/plugin-transform-modules-amd": "^7.24.7",
"@babel/plugin-transform-modules-commonjs": "^7.24.8",
"@babel/plugin-transform-modules-systemjs": "^7.25.0",
"@babel/plugin-transform-modules-umd": "^7.24.7",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7",
"@babel/plugin-transform-new-target": "^7.24.7",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7",
"@babel/plugin-transform-numeric-separator": "^7.24.7",
"@babel/plugin-transform-object-rest-spread": "^7.24.7",
"@babel/plugin-transform-object-super": "^7.24.7",
"@babel/plugin-transform-optional-catch-binding": "^7.24.7",
"@babel/plugin-transform-optional-chaining": "^7.24.8",
"@babel/plugin-transform-parameters": "^7.24.7",
"@babel/plugin-transform-private-methods": "^7.24.7",
"@babel/plugin-transform-private-property-in-object": "^7.24.7",
"@babel/plugin-transform-property-literals": "^7.24.7",
"@babel/plugin-transform-regenerator": "^7.24.7",
"@babel/plugin-transform-reserved-words": "^7.24.7",
"@babel/plugin-transform-shorthand-properties": "^7.24.7",
"@babel/plugin-transform-spread": "^7.24.7",
"@babel/plugin-transform-sticky-regex": "^7.24.7",
"@babel/plugin-transform-template-literals": "^7.24.7",
"@babel/plugin-transform-typeof-symbol": "^7.24.8",
"@babel/plugin-transform-unicode-escapes": "^7.24.7",
"@babel/plugin-transform-unicode-property-regex": "^7.24.7",
"@babel/plugin-transform-unicode-regex": "^7.24.7",
"@babel/plugin-transform-unicode-sets-regex": "^7.24.7",
"@babel/preset-modules": "0.1.6-no-external-plugins",
"babel-plugin-polyfill-corejs2": "^0.4.10",
"babel-plugin-polyfill-corejs3": "^0.10.4",
"babel-plugin-polyfill-regenerator": "^0.6.1",
"core-js-compat": "^3.31.0",
"core-js-compat": "^3.37.1",
"semver": "^6.3.1"
},
"engines": {
@@ -1747,16 +1848,17 @@
}
},
"node_modules/@babel/preset-react": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz",
"integrity": "sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.7.tgz",
"integrity": "sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.18.6",
"@babel/helper-validator-option": "^7.18.6",
"@babel/plugin-transform-react-display-name": "^7.18.6",
"@babel/plugin-transform-react-jsx": "^7.18.6",
"@babel/plugin-transform-react-jsx-development": "^7.18.6",
"@babel/plugin-transform-react-pure-annotations": "^7.18.6"
"@babel/helper-plugin-utils": "^7.24.7",
"@babel/helper-validator-option": "^7.24.7",
"@babel/plugin-transform-react-display-name": "^7.24.7",
"@babel/plugin-transform-react-jsx": "^7.24.7",
"@babel/plugin-transform-react-jsx-development": "^7.24.7",
"@babel/plugin-transform-react-pure-annotations": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1787,31 +1889,30 @@
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/@babel/template": {
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
"integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==",
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz",
"integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.23.5",
"@babel/parser": "^7.24.0",
"@babel/types": "^7.24.0"
"@babel/code-frame": "^7.24.7",
"@babel/parser": "^7.25.0",
"@babel/types": "^7.25.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz",
"integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==",
"version": "7.25.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz",
"integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.24.1",
"@babel/generator": "^7.24.1",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.24.1",
"@babel/types": "^7.24.0",
"@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.25.0",
"@babel/parser": "^7.25.3",
"@babel/template": "^7.25.0",
"@babel/types": "^7.25.2",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -1820,12 +1921,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
"integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
"integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20",
"@babel/helper-string-parser": "^7.24.8",
"@babel/helper-validator-identifier": "^7.24.7",
"to-fast-properties": "^2.0.0"
},
"engines": {
@@ -4552,15 +4654,6 @@
"node": ">=0.4.0"
}
},
"node_modules/adm-zip": {
"version": "0.4.16",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz",
"integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==",
"dev": true,
"engines": {
"node": ">=0.3.0"
}
},
"node_modules/after": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
@@ -5439,16 +5532,17 @@
"integrity": "sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA=="
},
"node_modules/babel-jest": {
"version": "26.0.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.0.0.tgz",
"integrity": "sha512-2AtcYOP4xhFn6TkvGmbEArNBcYLm/cTOIXB1a5j2juPOIC2U0nHEouMqYzgnPXgWC+CBK5RmYoGnwRt6eV4E8A==",
"version": "26.6.3",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz",
"integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jest/transform": "^26.0.0",
"@jest/types": "^26.0.0",
"@jest/transform": "^26.6.2",
"@jest/types": "^26.6.2",
"@types/babel__core": "^7.1.7",
"babel-plugin-istanbul": "^6.0.0",
"babel-preset-jest": "^26.0.0",
"babel-preset-jest": "^26.6.2",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.4",
"slash": "^3.0.0"
@@ -5465,6 +5559,7 @@
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -5480,6 +5575,7 @@
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -5496,6 +5592,7 @@
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
@@ -5507,13 +5604,15 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/babel-jest/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
@@ -5523,6 +5622,7 @@
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -5956,9 +6056,10 @@
}
},
"node_modules/backbone": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.1.tgz",
"integrity": "sha512-ADy1ztN074YkWbHi8ojJVFe3vAanO/lrzMGZWUClIP7oDD/Pjy2vrASraUP+2EVCfIiTtCW4FChVow01XneivA==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/backbone/-/backbone-1.6.0.tgz",
"integrity": "sha512-13PUjmsgw/49EowNcQvfG4gmczz1ximTMhUktj0Jfrjth0MVaTxehpU+qYYX4MxnuIuhmvBLC6/ayxuAGnOhbA==",
"license": "MIT",
"dependencies": {
"underscore": ">=1.8.3"
}
@@ -6199,9 +6300,9 @@
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow=="
},
"node_modules/browserslist": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
"integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==",
"funding": [
{
"type": "opencollective",
@@ -6216,11 +6317,12 @@
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001587",
"electron-to-chromium": "^1.4.668",
"node-releases": "^2.0.14",
"update-browserslist-db": "^1.0.13"
"caniuse-lite": "^1.0.30001646",
"electron-to-chromium": "^1.5.4",
"node-releases": "^2.0.18",
"update-browserslist-db": "^1.1.0"
},
"bin": {
"browserslist": "cli.js"
@@ -6499,9 +6601,9 @@
"integrity": "sha512-BS+RAD1DggiDlE2KaBUWKsMDuVmmh3hCM5LI0OW25mGlPttGLeOjDUa1DmZvJVFCXvtshY4BTyFgv31eFTLg8g=="
},
"node_modules/caniuse-lite": {
"version": "1.0.30001600",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz",
"integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==",
"version": "1.0.30001651",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz",
"integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==",
"funding": [
{
"type": "opencollective",
@@ -6515,7 +6617,8 @@
"type": "github",
"url": "https://github.com/sponsors/ai"
}
]
],
"license": "CC-BY-4.0"
},
"node_modules/capture-exit": {
"version": "2.0.0",
@@ -7288,7 +7391,8 @@
"node_modules/convert-source-map": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
"dev": true
},
"node_modules/cookie": {
"version": "0.3.1",
@@ -7324,11 +7428,12 @@
"hasInstallScript": true
},
"node_modules/core-js-compat": {
"version": "3.36.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz",
"integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==",
"version": "3.38.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.0.tgz",
"integrity": "sha512-75LAicdLa4OJVwFxFbQR3NdnZjNgX6ILpVcVzcC4T2smerB5lELMrJQQQoWV6TiuC/vlaFqgU2tKQx9w5s0e0A==",
"license": "MIT",
"dependencies": {
"browserslist": "^4.23.0"
"browserslist": "^4.23.3"
},
"funding": {
"type": "opencollective",
@@ -8130,15 +8235,6 @@
"urijs": "1.19.11"
}
},
"node_modules/edx-ui-toolkit/node_modules/backbone": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/backbone/-/backbone-1.6.0.tgz",
"integrity": "sha512-13PUjmsgw/49EowNcQvfG4gmczz1ximTMhUktj0Jfrjth0MVaTxehpU+qYYX4MxnuIuhmvBLC6/ayxuAGnOhbA==",
"license": "MIT",
"dependencies": {
"underscore": ">=1.8.3"
}
},
"node_modules/edx-ui-toolkit/node_modules/formatio": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz",
@@ -8204,9 +8300,10 @@
"dev": true
},
"node_modules/electron-to-chromium": {
"version": "1.4.717",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.717.tgz",
"integrity": "sha512-6Fmg8QkkumNOwuZ/5mIbMU9WI3H2fmn5ajcVya64I5Yr5CcNmO7vcLt0Y7c96DCiMO5/9G+4sI2r6eEvdg1F7A=="
"version": "1.5.6",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.6.tgz",
"integrity": "sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw==",
"license": "ISC"
},
"node_modules/email-prop-type": {
"version": "1.1.7",
@@ -8861,6 +8958,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
@@ -11419,6 +11517,13 @@
"node": ">= 4"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"dev": true,
"license": "MIT"
},
"node_modules/immutable": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
@@ -12746,14 +12851,15 @@
"license": "MIT"
},
"node_modules/jest": {
"version": "26.0.0",
"resolved": "https://registry.npmjs.org/jest/-/jest-26.0.0.tgz",
"integrity": "sha512-OtoG+cpcP+UXx+pQ7rzoQ11Pfb5+OUkrsNn5YPc0GU2HeBktgTANonUZEgT6cCgUHX7jUiuDIusDNTL4iNcWGQ==",
"version": "26.6.3",
"resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz",
"integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jest/core": "^26.0.0",
"@jest/core": "^26.6.3",
"import-local": "^3.0.2",
"jest-cli": "^26.0.0"
"jest-cli": "^26.6.3"
},
"bin": {
"jest": "bin/jest.js"
@@ -12925,28 +13031,6 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/jest-config/node_modules/babel-jest": {
"version": "26.6.3",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz",
"integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==",
"dev": true,
"dependencies": {
"@jest/transform": "^26.6.2",
"@jest/types": "^26.6.2",
"@types/babel__core": "^7.1.7",
"babel-plugin-istanbul": "^6.0.0",
"babel-preset-jest": "^26.6.2",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.4",
"slash": "^3.0.0"
},
"engines": {
"node": ">= 10.14.2"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/jest-config/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -13709,14 +13793,15 @@
}
},
"node_modules/jest-enzyme": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-6.0.2.tgz",
"integrity": "sha512-lf6tjFA5Lthcazqx1acwo/rTjIKIsOp2jbZCZSi1WmRX0JHOJXa5NfioaaJ4wJeIWoIShU/9Go0OjKCXiAzCAg==",
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-6.1.2.tgz",
"integrity": "sha512-+ds7r2ru3QkNJxelQ2tnC6d33pjUSsZHPD3v4TlnHlNMuGX3UKdxm5C46yZBvJICYBvIF+RFKBhLMM4evNM95Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"enzyme-matchers": "^6.0.2",
"enzyme-matchers": "^6.1.2",
"enzyme-to-json": "^3.3.0",
"jest-environment-enzyme": "^6.0.2"
"jest-environment-enzyme": "^6.1.2"
},
"peerDependencies": {
"enzyme": "3.x",
@@ -15251,6 +15336,59 @@
"node": ">=4.0"
}
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"dev": true,
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/jszip/node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true,
"license": "MIT"
},
"node_modules/jszip/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true,
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/jszip/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true,
"license": "MIT"
},
"node_modules/jszip/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/karma": {
"version": "0.13.22",
"resolved": "https://registry.npmjs.org/karma/-/karma-0.13.22.tgz",
@@ -15555,10 +15693,11 @@
}
},
"node_modules/karma-junit-reporter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-1.1.0.tgz",
"integrity": "sha512-Iucb7SsKqYxn7azHltDHSI2wNzZ7MEzCQjZPNStEAlxyJMkX5OBWd0D6Sesy/mMKh9FltLDqe23hsZFyNvIKxQ==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-1.2.0.tgz",
"integrity": "sha512-FeuLOKlXNtJhIQK3oQASbO5QOib762CEHV8+L9wwTQpiZJgp7xKg3sNno66rL5bQPV2soG6fJdAFWqqnMJuh2w==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-is-absolute": "^1.0.0",
"xmlbuilder": "8.2.2"
@@ -15591,12 +15730,13 @@
}
},
"node_modules/karma-sourcemap-loader": {
"version": "0.3.7",
"resolved": "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.7.tgz",
"integrity": "sha512-zu99gTgKf6KhLg14c+e+SHb297A8MX9TRxjW+USEmk/xq2ULnyvkfZuOZOLHU6522QfJai1SZznP/brNwivtDg==",
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.4.0.tgz",
"integrity": "sha512-xCRL3/pmhAYF3I6qOrcn0uhbQevitc2DERMPH82FMnG+4WReoGcGFZb1pURf2a5apyrOHRdvD+O6K7NljqKHyA==",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.1.2"
"graceful-fs": "^4.2.10"
}
},
"node_modules/karma-spec-reporter": {
@@ -15790,6 +15930,16 @@
"node": ">= 0.8.0"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -17143,9 +17293,10 @@
"optional": true
},
"node_modules/node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
"license": "MIT"
},
"node_modules/nopt": {
"version": "3.0.6",
@@ -17713,6 +17864,13 @@
"node": ">=6"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true,
"license": "(MIT AND Zlib)"
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -17946,9 +18104,10 @@
"dev": true
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -19971,18 +20130,31 @@
}
},
"node_modules/react-test-renderer": {
"version": "16.4.0",
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.4.0.tgz",
"integrity": "sha512-Seh1t9xFY6TKiV/hRlPzUkqX1xHOiKIMsctfU0cggo1ajsLjoIJFL520LlrxV+4/VIj+clrCeH6s/aVv/vTStg==",
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz",
"integrity": "sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==",
"dev": true,
"license": "MIT",
"dependencies": {
"fbjs": "^0.8.16",
"object-assign": "^4.1.1",
"prop-types": "^15.6.0",
"react-is": "^16.4.0"
"prop-types": "^15.6.2",
"react-is": "^16.8.6",
"scheduler": "^0.19.1"
},
"peerDependencies": {
"react": "^16.0.0"
"react": "^16.14.0"
}
},
"node_modules/react-test-renderer/node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/react-transition-group": {
@@ -20862,9 +21034,10 @@
}
},
"node_modules/requirejs": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz",
"integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==",
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.7.tgz",
"integrity": "sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw==",
"license": "MIT",
"bin": {
"r_js": "bin/r.js",
"r.js": "bin/r.js"
@@ -21968,12 +22141,13 @@
"integrity": "sha512-qGVDoreyYiP1pkQnbnFAUIS5AjenNwwQBdl7zeos9etl+hYKWahjRTfzAZZYBv5xNHx7vNKCmaLDQZ6Fr2AEXg=="
},
"node_modules/selenium-webdriver": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.4.0.tgz",
"integrity": "sha512-Bfq9FX33fFSj0F26BaaZllQBZl3d9FykVaH4CEJfwtN43+V8jrTpuhfiR5Zm9nkXjMbcEQz2EKWAjNzV8Hkqbw==",
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz",
"integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"adm-zip": "^0.4.7",
"jszip": "^3.1.3",
"rimraf": "^2.5.4",
"tmp": "0.0.30",
"xml2js": "^0.4.17"
@@ -21986,7 +22160,9 @@
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"glob": "^7.1.3"
},
@@ -24613,9 +24789,9 @@
}
},
"node_modules/update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
"integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
"funding": [
{
"type": "opencollective",
@@ -24630,9 +24806,10 @@
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"escalade": "^3.1.1",
"picocolors": "^1.0.0"
"escalade": "^3.1.2",
"picocolors": "^1.0.1"
},
"bin": {
"update-browserslist-db": "cli.js"

View File

@@ -15,11 +15,11 @@
"watch-sass": "scripts/watch_sass.sh"
},
"dependencies": {
"@babel/core": "7.19.0",
"@babel/core": "7.25.2",
"@babel/plugin-proposal-object-rest-spread": "^7.18.9",
"@babel/plugin-transform-object-assign": "^7.18.6",
"@babel/preset-env": "^7.19.0",
"@babel/preset-react": "7.18.6",
"@babel/preset-react": "7.24.7",
"@edx/brand-edx.org": "^2.0.7",
"@edx/edx-bootstrap": "1.0.4",
"@edx/edx-proctoring": "^4.18.1",
@@ -29,7 +29,7 @@
"babel-loader": "^9.1.3",
"babel-plugin-transform-class-properties": "6.24.1",
"babel-polyfill": "6.26.0",
"backbone": "1.4.1",
"backbone": "1.6.0",
"backbone-associations": "0.6.2",
"backbone.paginator": "2.0.8",
"bootstrap": "4.0.0",
@@ -65,7 +65,7 @@
"react-slick": "0.29.0",
"redux": "3.7.2",
"redux-thunk": "2.2.0",
"requirejs": "2.3.6",
"requirejs": "2.3.7",
"rtlcss": "2.6.2",
"sass": "^1.54.8",
"sass-loader": "^14.1.1",
@@ -85,29 +85,29 @@
"@edx/eslint-config": "^3.1.1",
"@edx/mockprock": "github:openedx/mockprock#3ad18c6888e6521e9bf7a4df0db6f8579b928235",
"@edx/stylelint-config-edx": "2.3.3",
"babel-jest": "26.0.0",
"babel-jest": "26.6.3",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.8",
"eslint-import-resolver-webpack": "0.13.8",
"jasmine-core": "2.6.4",
"jasmine-jquery": "git+https://git@github.com/velesin/jasmine-jquery.git#ebad463d592d3fea00c69f26ea18a930e09c7b58",
"jest": "26.0.0",
"jest-enzyme": "6.0.2",
"jest": "26.6.3",
"jest-enzyme": "6.1.2",
"karma": "0.13.22",
"karma-chrome-launcher": "0.2.3",
"karma-coverage": "0.5.5",
"karma-firefox-launcher": "0.1.7",
"karma-jasmine": "0.3.8",
"karma-jasmine-html-reporter": "0.2.2",
"karma-junit-reporter": "1.1.0",
"karma-junit-reporter": "1.2.0",
"karma-requirejs": "0.2.6",
"karma-selenium-webdriver-launcher": "github:openedx/karma-selenium-webdriver-launcher#0.0.4-openedx.0",
"karma-sourcemap-loader": "0.3.7",
"karma-sourcemap-loader": "0.4.0",
"karma-spec-reporter": "0.0.36",
"karma-webpack": "^5.0.1",
"plato": "1.7.0",
"react-test-renderer": "16.4.0",
"selenium-webdriver": "3.4.0",
"react-test-renderer": "16.14.0",
"selenium-webdriver": "3.6.0",
"sinon": "2.4.1",
"squirejs": "0.1.0",
"string-replace-loader": "^3.1.0",

View File

@@ -26,15 +26,6 @@ elasticsearch<7.14.0
# django-simple-history>3.0.0 adds indexing and causes a lot of migrations to be affected
# opentelemetry requires version 6.x at the moment:
# https://github.com/open-telemetry/opentelemetry-python/issues/3570
# Normally this could be added as a constraint in edx-django-utils, where we're
# adding the opentelemetry dependency. However, when we compile pip-tools.txt,
# that uses version 7.x, and then there's no undoing that when compiling base.txt.
# So we need to pin it globally, for now.
# Ticket for unpinning: https://github.com/openedx/edx-lint/issues/407
importlib-metadata<7
# Cause: https://github.com/openedx/event-tracking/pull/290
# event-tracking 2.4.1 upgrades to pymongo 4.4.0 which is not supported on edx-platform.
# We will pin event-tracking to do not break existing installations

View File

@@ -12,6 +12,12 @@
# This file contains all common constraints for edx-repos
-c common_constraints.txt
# Date: 2024-08-21
# Description: This is the major upgrade of algoliasearch python client and it will
# break one of the edX' platform plugin, so we need to make that compatible first.
# Ticket: https://github.com/openedx/edx-platform/issues/35334
algoliasearch<4.0.0
# As it is not clarified what exact breaking changes will be introduced as per
# the next major release, ensure the installed version is within boundaries.
celery>=5.2.2,<6.0.0
@@ -20,7 +26,7 @@ celery>=5.2.2,<6.0.0
# The team that owns this package will manually bump this package rather than having it pulled in automatically.
# This is to allow them to better control its deployment and to do it in a process that works better
# for them.
edx-enterprise==4.21.9
edx-enterprise==4.23.9
# Stay on LTS version, remove once this is added to common constraint
Django<5.0
@@ -87,7 +93,7 @@ libsass==0.10.0
click==8.1.6
# pinning this version to avoid updates while the library is being developed
openedx-learning==0.10.0
openedx-learning==0.10.1
# Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise.
openai<=0.28.1
@@ -130,3 +136,13 @@ numpy<2.0.0
# Two lines were added in 1.14.4 that make file_exists_in_storage function always return False,
# as the default value of AWS_S3_FILE_OVERWRITE is True
django-storages<1.14.4
# social-auth-app-django 5.4.2 introduces a new migration that will not play nicely with large installations. This will touch
# user tables, which are quite large, especially on instances like edx.org.
# We are pinning this until after all the smaller migrations get handled and then we can migrate this all at once.
# Ticket to unpin: https://github.com/edx/edx-arch-experiments/issues/760
social-auth-app-django<=5.4.1
# Xblock==5.0.0 changed how entrypoints were loaded, breaking a workaround for overriding blocks.
# See ticket: https://github.com/openedx/XBlock/issues/777
xblock[django]==4.0.1

View File

@@ -4,7 +4,7 @@
#
# make upgrade
#
cffi==1.16.0
cffi==1.17.0
# via cryptography
chem==1.3.0
# via -r requirements/edx-sandbox/base.in
@@ -16,7 +16,7 @@ codejail-includes==1.0.0
# via -r requirements/edx-sandbox/base.in
contourpy==1.2.1
# via matplotlib
cryptography==42.0.8
cryptography==43.0.0
# via -r requirements/edx-sandbox/base.in
cycler==0.12.1
# via matplotlib
@@ -35,13 +35,13 @@ markupsafe==2.1.5
# via
# chem
# openedx-calc
matplotlib==3.9.1
matplotlib==3.9.2
# via -r requirements/edx-sandbox/base.in
mpmath==1.3.0
# via sympy
networkx==3.3
# via -r requirements/edx-sandbox/base.in
nltk==3.8.1
nltk==3.9.1
# via
# -r requirements/edx-sandbox/base.in
# chem
@@ -71,7 +71,7 @@ python-dateutil==2.9.0.post0
# via matplotlib
random2==1.0.2
# via -r requirements/edx-sandbox/base.in
regex==2024.5.15
regex==2024.7.24
# via nltk
scipy==1.14.0
# via
@@ -82,9 +82,9 @@ six==1.16.0
# via
# codejail-includes
# python-dateutil
sympy==1.13.0
sympy==1.13.2
# via
# -r requirements/edx-sandbox/base.in
# openedx-calc
tqdm==4.66.4
tqdm==4.66.5
# via nltk

View File

@@ -8,14 +8,18 @@
# via -r requirements/edx/github.in
acid-xblock==0.3.1
# via -r requirements/edx/kernel.in
aiohttp==3.9.5
aiohappyeyeballs==2.4.0
# via aiohttp
aiohttp==3.10.5
# via
# geoip2
# openai
aiosignal==1.3.1
# via aiohttp
algoliasearch==3.0.0
# via -r requirements/edx/bundled.in
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/bundled.in
amqp==5.2.0
# via kombu
analytics-python==1.4.post1
@@ -33,7 +37,7 @@ asgiref==3.8.1
# django-countries
asn1crypto==1.5.1
# via snowflake-connector-python
attrs==23.2.0
attrs==24.2.0
# via
# -r requirements/edx/kernel.in
# aiohttp
@@ -43,14 +47,14 @@ attrs==23.2.0
# openedx-events
# openedx-learning
# referencing
babel==2.15.0
babel==2.16.0
# via
# -r requirements/edx/kernel.in
# enmerkar
# enmerkar-underscore
backoff==1.10.0
# via analytics-python
bcrypt==4.1.3
bcrypt==4.2.0
# via paramiko
beautifulsoup4==4.12.3
# via pynliner
@@ -66,19 +70,23 @@ bleach[css]==6.1.0
# xblock-poll
boto==2.49.0
# via -r requirements/edx/kernel.in
boto3==1.34.144
boto3==1.35.1
# via
# -r requirements/edx/kernel.in
# django-ses
# fs-s3fs
# ora2
botocore==1.34.144
botocore==1.35.1
# via
# -r requirements/edx/kernel.in
# boto3
# s3transfer
bridgekeeper==0.9
# via -r requirements/edx/kernel.in
cachecontrol==0.14.0
# via firebase-admin
cachetools==5.5.0
# via google-auth
camel-converter[pydantic]==3.1.2
# via meilisearch
celery==5.4.0
@@ -98,7 +106,7 @@ certifi==2024.7.4
# py2neo
# requests
# snowflake-connector-python
cffi==1.16.0
cffi==1.17.0
# via
# cryptography
# pynacl
@@ -160,7 +168,7 @@ defusedxml==0.7.1
# ora2
# python3-openid
# social-auth-core
django==4.2.14
django==4.2.15
# via
# -c requirements/edx/../common_constraints.txt
# -c requirements/edx/../constraints.txt
@@ -179,6 +187,7 @@ django==4.2.14
# django-multi-email-field
# django-mysql
# django-oauth-toolkit
# django-push-notifications
# django-sekizai
# django-ses
# django-statici18n
@@ -262,7 +271,7 @@ django-crum==0.7.9
# super-csv
django-fernet-fields-v2==0.9
# via edx-enterprise
django-filter==24.2
django-filter==24.3
# via
# -r requirements/edx/kernel.in
# edx-enterprise
@@ -311,6 +320,8 @@ django-object-actions==4.2.0
# via edx-enterprise
django-pipeline==3.1.0
# via -r requirements/edx/kernel.in
django-push-notifications==3.1.0
# via edx-ace
django-ratelimit==4.1.0
# via -r requirements/edx/kernel.in
django-sekizai==4.1.0
@@ -390,7 +401,7 @@ drf-yasg==1.21.7
# via
# django-user-tasks
# edx-api-doc-tools
edx-ace==1.9.1
edx-ace==1.11.1
# via -r requirements/edx/kernel.in
edx-api-doc-tools==1.8.0
# via
@@ -418,7 +429,7 @@ edx-celeryutils==1.3.0
# super-csv
edx-codejail==3.4.1
# via -r requirements/edx/kernel.in
edx-completion==4.6.6
edx-completion==4.6.7
# via -r requirements/edx/kernel.in
edx-django-release-util==1.4.0
# via
@@ -427,7 +438,7 @@ edx-django-release-util==1.4.0
# edxval
edx-django-sites-extensions==4.2.0
# via -r requirements/edx/kernel.in
edx-django-utils==5.14.2
edx-django-utils==5.15.0
# via
# -r requirements/edx/kernel.in
# django-config-models
@@ -456,11 +467,11 @@ edx-drf-extensions==10.3.0
# edx-when
# edxval
# openedx-learning
edx-enterprise==4.21.9
edx-enterprise==4.23.9
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/kernel.in
edx-event-bus-kafka==5.7.0
edx-event-bus-kafka==5.8.1
# via -r requirements/edx/kernel.in
edx-event-bus-redis==0.5.0
# via -r requirements/edx/kernel.in
@@ -502,11 +513,11 @@ edx-rest-api-client==5.7.1
# -r requirements/edx/kernel.in
# edx-enterprise
# edx-proctoring
edx-search==3.9.1
edx-search==4.0.0
# via -r requirements/edx/kernel.in
edx-sga==0.25.0
# via -r requirements/edx/bundled.in
edx-submissions==3.7.5
edx-submissions==3.7.7
# via
# -r requirements/edx/kernel.in
# ora2
@@ -551,6 +562,8 @@ fastavro==1.9.5
# via openedx-events
filelock==3.15.4
# via snowflake-connector-python
firebase-admin==6.5.0
# via edx-ace
frozenlist==1.4.1
# via
# aiohttp
@@ -571,7 +584,50 @@ geoip2==4.8.0
# via -r requirements/edx/kernel.in
glob2==0.7
# via -r requirements/edx/kernel.in
gunicorn==22.0.0
google-api-core[grpc]==2.19.1
# via
# firebase-admin
# google-api-python-client
# google-cloud-core
# google-cloud-firestore
# google-cloud-storage
google-api-python-client==2.141.0
# via firebase-admin
google-auth==2.34.0
# via
# google-api-core
# google-api-python-client
# google-auth-httplib2
# google-cloud-core
# google-cloud-firestore
# google-cloud-storage
google-auth-httplib2==0.2.0
# via google-api-python-client
google-cloud-core==2.4.1
# via
# google-cloud-firestore
# google-cloud-storage
google-cloud-firestore==2.17.2
# via firebase-admin
google-cloud-storage==2.18.2
# via firebase-admin
google-crc32c==1.5.0
# via
# google-cloud-storage
# google-resumable-media
google-resumable-media==2.7.2
# via google-cloud-storage
googleapis-common-protos==1.63.2
# via
# google-api-core
# grpcio-status
grpcio==1.65.5
# via
# google-api-core
# grpcio-status
grpcio-status==1.65.5
# via google-api-core
gunicorn==23.0.0
# via -r requirements/edx/kernel.in
help-tokens==2.4.0
# via -r requirements/edx/kernel.in
@@ -579,6 +635,10 @@ html5lib==1.1
# via
# -r requirements/edx/kernel.in
# ora2
httplib2==0.22.0
# via
# google-api-python-client
# google-auth-httplib2
icalendar==5.0.13
# via -r requirements/edx/kernel.in
idna==3.7
@@ -588,10 +648,8 @@ idna==3.7
# requests
# snowflake-connector-python
# yarl
importlib-metadata==6.11.0
# via
# -c requirements/edx/../common_constraints.txt
# -r requirements/edx/kernel.in
importlib-metadata==8.3.0
# via -r requirements/edx/kernel.in
inflection==0.5.1
# via
# drf-spectacular
@@ -610,7 +668,7 @@ jmespath==1.0.1
# botocore
joblib==1.4.2
# via nltk
jsondiff==2.1.2
jsondiff==2.2.0
# via edx-enterprise
jsonfield==3.1.0
# via
@@ -631,7 +689,7 @@ jwcrypto==1.5.6
# via
# django-oauth-toolkit
# pylti1p3
kombu==5.3.7
kombu==5.4.0
# via celery
laboratory==1.0.2
# via -r requirements/edx/kernel.in
@@ -699,23 +757,25 @@ monotonic==1.6
# via
# analytics-python
# py2neo
more-itertools==10.3.0
more-itertools==10.4.0
# via cssutils
mpmath==1.3.0
# via sympy
msgpack==1.0.8
# via cachecontrol
multidict==6.0.5
# via
# aiohttp
# yarl
mysqlclient==2.2.4
# via -r requirements/edx/kernel.in
newrelic==9.12.0
newrelic==9.13.0
# via
# -r requirements/edx/bundled.in
# edx-django-utils
nh3==0.2.18
# via -r requirements/edx/kernel.in
nltk==3.8.1
nltk==3.9.1
# via chem
nodeenv==1.9.1
# via -r requirements/edx/kernel.in
@@ -763,7 +823,7 @@ openedx-filters==1.9.0
# -r requirements/edx/kernel.in
# lti-consumer-xblock
# ora2
openedx-learning==0.10.0
openedx-learning==0.10.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/kernel.in
@@ -773,7 +833,7 @@ optimizely-sdk==4.1.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/bundled.in
ora2==6.11.1
ora2==6.11.2
# via -r requirements/edx/bundled.in
packaging==24.1
# via
@@ -783,7 +843,7 @@ packaging==24.1
# snowflake-connector-python
pansi==2020.7.3
# via py2neo
paramiko==3.4.0
paramiko==3.4.1
# via edx-enterprise
path==16.11.0
# via
@@ -819,6 +879,17 @@ polib==1.2.0
# via edx-i18n-tools
prompt-toolkit==3.0.47
# via click-repl
proto-plus==1.24.0
# via
# google-api-core
# google-cloud-firestore
protobuf==5.27.3
# via
# google-api-core
# google-cloud-firestore
# googleapis-common-protos
# grpcio-status
# proto-plus
psutil==6.0.0
# via
# -r requirements/edx/paver.txt
@@ -828,7 +899,12 @@ py2neo @ https://github.com/overhangio/py2neo/releases/download/2021.2.3/py2neo-
# -c requirements/edx/../constraints.txt
# -r requirements/edx/bundled.in
pyasn1==0.6.0
# via pgpy
# via
# pgpy
# pyasn1-modules
# rsa
pyasn1-modules==0.4.0
# via google-auth
pycountry==24.6.1
# via -r requirements/edx/kernel.in
pycparser==2.22
@@ -852,7 +928,7 @@ pyjwkest==1.4.2
# -r requirements/edx/kernel.in
# edx-token-utils
# lti-consumer-xblock
pyjwt[crypto]==2.8.0
pyjwt[crypto]==2.9.0
# via
# -r requirements/edx/kernel.in
# drf-jwt
@@ -860,6 +936,7 @@ pyjwt[crypto]==2.8.0
# edx-drf-extensions
# edx-proctoring
# edx-rest-api-client
# firebase-admin
# pylti1p3
# snowflake-connector-python
# social-auth-core
@@ -884,13 +961,14 @@ pynacl==1.5.0
# paramiko
pynliner==0.8.0
# via -r requirements/edx/kernel.in
pyopenssl==24.1.0
pyopenssl==24.2.1
# via
# optimizely-sdk
# snowflake-connector-python
pyparsing==3.1.2
# via
# chem
# httplib2
# openedx-calc
pyrsistent==0.20.0
# via optimizely-sdk
@@ -946,7 +1024,7 @@ pytz==2024.1
# xblock
pyuca==1.2
# via -r requirements/edx/kernel.in
pyyaml==6.0.1
pyyaml==6.0.2
# via
# -r requirements/edx/kernel.in
# code-annotations
@@ -960,7 +1038,7 @@ random2==1.0.2
# via -r requirements/edx/kernel.in
recommender-xblock==2.2.0
# via -r requirements/edx/bundled.in
redis==5.0.7
redis==5.0.8
# via
# -r requirements/edx/kernel.in
# walrus
@@ -968,19 +1046,22 @@ referencing==0.35.1
# via
# jsonschema
# jsonschema-specifications
regex==2024.5.15
regex==2024.7.24
# via nltk
requests==2.32.3
# via
# -r requirements/edx/paver.txt
# algoliasearch
# analytics-python
# cachecontrol
# django-oauth-toolkit
# edx-bulk-grades
# edx-drf-extensions
# edx-enterprise
# edx-rest-api-client
# geoip2
# google-api-core
# google-cloud-storage
# mailsnake
# meilisearch
# openai
@@ -998,10 +1079,12 @@ requests-oauthlib==2.0.0
# via
# -r requirements/edx/kernel.in
# social-auth-core
rpds-py==0.19.0
rpds-py==0.20.0
# via
# jsonschema
# referencing
rsa==4.9
# via google-auth
rules==3.4
# via
# -r requirements/edx/kernel.in
@@ -1018,9 +1101,9 @@ scipy==1.14.0
# openedx-calc
semantic-version==2.10.0
# via edx-drf-extensions
shapely==2.0.5
shapely==2.0.6
# via -r requirements/edx/kernel.in
simplejson==3.19.2
simplejson==3.19.3
# via
# -r requirements/edx/kernel.in
# sailthru-client
@@ -1061,10 +1144,11 @@ slumber==0.7.1
# edx-bulk-grades
# edx-enterprise
# edx-rest-api-client
snowflake-connector-python==3.11.0
snowflake-connector-python==3.12.0
# via edx-enterprise
social-auth-app-django==5.4.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/kernel.in
# edx-auth-backends
social-auth-core==4.5.4
@@ -1080,7 +1164,7 @@ sortedcontainers==2.4.0
# via
# -r requirements/edx/kernel.in
# snowflake-connector-python
soupsieve==2.5
soupsieve==2.6
# via beautifulsoup4
sqlparse==0.5.1
# via django
@@ -1097,7 +1181,7 @@ stevedore==5.2.0
# edx-opaque-keys
super-csv==3.2.0
# via edx-bulk-grades
sympy==1.13.0
sympy==1.13.2
# via openedx-calc
testfixtures==8.3.0
# via edx-enterprise
@@ -1105,9 +1189,9 @@ text-unidecode==1.3
# via python-slugify
tinycss2==1.2.1
# via bleach
tomlkit==0.13.0
tomlkit==0.13.2
# via snowflake-connector-python
tqdm==4.66.4
tqdm==4.66.5
# via
# nltk
# openai
@@ -1131,6 +1215,7 @@ uritemplate==4.1.1
# via
# drf-spectacular
# drf-yasg
# google-api-python-client
urllib3==1.26.19
# via
# -c requirements/edx/../constraints.txt
@@ -1150,7 +1235,7 @@ voluptuous==0.15.2
# via ora2
walrus==0.9.4
# via edx-event-bus-redis
watchdog==4.0.1
watchdog==4.0.2
# via -r requirements/edx/paver.txt
wcwidth==0.2.13
# via prompt-toolkit
@@ -1167,7 +1252,7 @@ webencodings==0.5.1
# bleach
# html5lib
# tinycss2
webob==1.8.7
webob==1.8.8
# via
# -r requirements/edx/kernel.in
# xblock
@@ -1175,6 +1260,7 @@ wrapt==1.16.0
# via -r requirements/edx/paver.txt
xblock[django]==4.0.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/kernel.in
# acid-xblock
# crowdsourcehinter-xblock
@@ -1206,7 +1292,7 @@ xss-utils==0.6.0
# via -r requirements/edx/kernel.in
yarl==1.9.4
# via aiohttp
zipp==3.19.2
zipp==3.20.0
# via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:

View File

@@ -6,9 +6,9 @@
#
chardet==5.2.0
# via diff-cover
coverage==7.6.0
coverage==7.6.1
# via -r requirements/edx/coverage.in
diff-cover==9.1.0
diff-cover==9.1.1
# via -r requirements/edx/coverage.in
jinja2==3.1.4
# via diff-cover

View File

@@ -16,7 +16,12 @@ acid-xblock==0.3.1
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
aiohttp==3.9.5
aiohappyeyeballs==2.4.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# aiohttp
aiohttp==3.10.5
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -27,12 +32,13 @@ aiosignal==1.3.1
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# aiohttp
alabaster==0.7.16
alabaster==1.0.0
# via
# -r requirements/edx/doc.txt
# sphinx
algoliasearch==3.0.0
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
amqp==5.2.0
@@ -57,9 +63,7 @@ annotated-types==0.7.0
anyio==4.4.0
# via
# -r requirements/edx/testing.txt
# httpcore
# starlette
# watchfiles
appdirs==1.4.4
# via
# -r requirements/edx/doc.txt
@@ -82,7 +86,7 @@ astroid==2.13.5
# -r requirements/edx/testing.txt
# pylint
# pylint-celery
attrs==23.2.0
attrs==24.2.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -93,7 +97,7 @@ attrs==23.2.0
# openedx-events
# openedx-learning
# referencing
babel==2.15.0
babel==2.16.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -106,7 +110,7 @@ backoff==1.10.0
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# analytics-python
bcrypt==4.1.3
bcrypt==4.2.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -136,14 +140,14 @@ boto==2.49.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
boto3==1.34.144
boto3==1.35.1
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# django-ses
# fs-s3fs
# ora2
botocore==1.34.144
botocore==1.35.1
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -157,9 +161,16 @@ build==1.2.1
# via
# -r requirements/edx/../pip-tools.txt
# pip-tools
cachetools==5.4.0
cachecontrol==0.14.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# firebase-admin
cachetools==5.5.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# google-auth
# tox
camel-converter[pydantic]==3.1.2
# via
@@ -182,16 +193,15 @@ certifi==2024.7.4
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# elasticsearch
# httpcore
# httpx
# py2neo
# requests
# snowflake-connector-python
cffi==1.16.0
cffi==1.17.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# cryptography
# pact-python
# pynacl
# snowflake-connector-python
chardet==5.2.0
@@ -232,7 +242,6 @@ click==8.1.6
# nltk
# pact-python
# pip-tools
# typer
# user-util
# uvicorn
click-didyoumean==0.3.1
@@ -269,7 +278,7 @@ colorama==0.4.6
# via
# -r requirements/edx/testing.txt
# tox
coverage[toml]==7.6.0
coverage[toml]==7.6.1
# via
# -r requirements/edx/testing.txt
# pytest-cov
@@ -314,7 +323,7 @@ defusedxml==0.7.1
# ora2
# python3-openid
# social-auth-core
diff-cover==9.1.0
diff-cover==9.1.1
# via -r requirements/edx/testing.txt
dill==0.3.8
# via
@@ -324,7 +333,7 @@ distlib==0.3.8
# via
# -r requirements/edx/testing.txt
# virtualenv
django==4.2.14
django==4.2.15
# via
# -c requirements/edx/../common_constraints.txt
# -c requirements/edx/../constraints.txt
@@ -345,6 +354,7 @@ django==4.2.14
# django-multi-email-field
# django-mysql
# django-oauth-toolkit
# django-push-notifications
# django-sekizai
# django-ses
# django-statici18n
@@ -451,7 +461,7 @@ django-fernet-fields-v2==0.9
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# edx-enterprise
django-filter==24.2
django-filter==24.3
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -520,6 +530,11 @@ django-pipeline==3.1.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
django-push-notifications==3.1.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# edx-ace
django-ratelimit==4.1.0
# via
# -r requirements/edx/doc.txt
@@ -561,7 +576,7 @@ django-stubs==1.16.0
# -c requirements/edx/../constraints.txt
# -r requirements/edx/development.in
# djangorestframework-stubs
django-stubs-ext==5.0.2
django-stubs-ext==5.0.4
# via django-stubs
django-user-tasks==3.2.0
# via
@@ -616,7 +631,6 @@ dnspython==2.6.1
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# email-validator
# pymongo
docutils==0.21.2
# via
@@ -643,7 +657,7 @@ drf-yasg==1.21.7
# -r requirements/edx/testing.txt
# django-user-tasks
# edx-api-doc-tools
edx-ace==1.9.1
edx-ace==1.11.1
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -682,7 +696,7 @@ edx-codejail==3.4.1
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
edx-completion==4.6.6
edx-completion==4.6.7
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -696,7 +710,7 @@ edx-django-sites-extensions==4.2.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
edx-django-utils==5.14.2
edx-django-utils==5.15.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -727,12 +741,12 @@ edx-drf-extensions==10.3.0
# edx-when
# edxval
# openedx-learning
edx-enterprise==4.21.9
edx-enterprise==4.23.9
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
edx-event-bus-kafka==5.7.0
edx-event-bus-kafka==5.8.1
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -792,7 +806,7 @@ edx-rest-api-client==5.7.1
# -r requirements/edx/testing.txt
# edx-enterprise
# edx-proctoring
edx-search==3.9.1
edx-search==4.0.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -800,7 +814,7 @@ edx-sga==0.25.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
edx-submissions==3.7.5
edx-submissions==3.7.7
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -842,10 +856,6 @@ elasticsearch==7.13.4
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# edx-search
email-validator==2.2.0
# via
# -r requirements/edx/testing.txt
# fastapi
enmerkar==0.7.1
# via
# -r requirements/edx/doc.txt
@@ -867,20 +877,16 @@ execnet==2.1.1
# via
# -r requirements/edx/testing.txt
# pytest-xdist
factory-boy==3.3.0
factory-boy==3.3.1
# via -r requirements/edx/testing.txt
faker==26.0.0
faker==27.0.0
# via
# -r requirements/edx/testing.txt
# factory-boy
fastapi==0.111.1
fastapi==0.112.1
# via
# -r requirements/edx/testing.txt
# pact-python
fastapi-cli==0.0.4
# via
# -r requirements/edx/testing.txt
# fastapi
fastavro==1.9.5
# via
# -r requirements/edx/doc.txt
@@ -893,6 +899,11 @@ filelock==3.15.4
# snowflake-connector-python
# tox
# virtualenv
firebase-admin==6.5.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# edx-ace
freezegun==1.5.1
# via -r requirements/edx/testing.txt
frozenlist==1.4.1
@@ -932,18 +943,90 @@ glob2==0.7
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
google-api-core[grpc]==2.19.1
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# firebase-admin
# google-api-python-client
# google-cloud-core
# google-cloud-firestore
# google-cloud-storage
google-api-python-client==2.141.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# firebase-admin
google-auth==2.34.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# google-api-core
# google-api-python-client
# google-auth-httplib2
# google-cloud-core
# google-cloud-firestore
# google-cloud-storage
google-auth-httplib2==0.2.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# google-api-python-client
google-cloud-core==2.4.1
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# google-cloud-firestore
# google-cloud-storage
google-cloud-firestore==2.17.2
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# firebase-admin
google-cloud-storage==2.18.2
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# firebase-admin
google-crc32c==1.5.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# google-cloud-storage
# google-resumable-media
google-resumable-media==2.7.2
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# google-cloud-storage
googleapis-common-protos==1.63.2
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# google-api-core
# grpcio-status
grimp==3.4.1
# via
# -r requirements/edx/testing.txt
# import-linter
gunicorn==22.0.0
grpcio==1.65.5
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# google-api-core
# grpcio-status
grpcio-status==1.65.5
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# google-api-core
gunicorn==23.0.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
h11==0.14.0
# via
# -r requirements/edx/testing.txt
# httpcore
# uvicorn
help-tokens==2.4.0
# via
@@ -954,21 +1037,14 @@ html5lib==1.1
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# ora2
httpcore==0.16.3
httplib2==0.22.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# httpx
# google-api-python-client
# google-auth-httplib2
httpretty==1.1.4
# via -r requirements/edx/testing.txt
httptools==0.6.1
# via
# -r requirements/edx/testing.txt
# uvicorn
httpx==0.23.3
# via
# -r requirements/edx/testing.txt
# fastapi
# pact-python
icalendar==5.0.13
# via
# -r requirements/edx/doc.txt
@@ -978,10 +1054,8 @@ idna==3.7
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# anyio
# email-validator
# optimizely-sdk
# requests
# rfc3986
# snowflake-connector-python
# yarl
imagesize==1.4.1
@@ -990,9 +1064,8 @@ imagesize==1.4.1
# sphinx
import-linter==2.0
# via -r requirements/edx/testing.txt
importlib-metadata==6.11.0
importlib-metadata==8.3.0
# via
# -c requirements/edx/../common_constraints.txt
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
inflection==0.5.1
@@ -1029,7 +1102,6 @@ jinja2==3.1.4
# -r requirements/edx/testing.txt
# code-annotations
# diff-cover
# fastapi
# sphinx
jmespath==1.0.1
# via
@@ -1042,7 +1114,7 @@ joblib==1.4.2
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# nltk
jsondiff==2.1.2
jsondiff==2.2.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -1075,7 +1147,7 @@ jwcrypto==1.5.6
# -r requirements/edx/testing.txt
# django-oauth-toolkit
# pylti1p3
kombu==5.3.7
kombu==5.4.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -1146,10 +1218,6 @@ markdown==3.3.7
# openedx-django-wiki
# staff-graded-xblock
# xblock-poll
markdown-it-py==3.0.0
# via
# -r requirements/edx/testing.txt
# rich
markupsafe==2.1.5
# via
# -r requirements/edx/doc.txt
@@ -1168,10 +1236,6 @@ mccabe==0.7.0
# via
# -r requirements/edx/testing.txt
# pylint
mdurl==0.1.2
# via
# -r requirements/edx/testing.txt
# markdown-it-py
meilisearch==0.31.4
# via
# -r requirements/edx/doc.txt
@@ -1194,7 +1258,7 @@ monotonic==1.6
# -r requirements/edx/testing.txt
# analytics-python
# py2neo
more-itertools==10.3.0
more-itertools==10.4.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -1204,13 +1268,18 @@ mpmath==1.3.0
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# sympy
msgpack==1.0.8
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# cachecontrol
multidict==6.0.5
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# aiohttp
# yarl
mypy==1.10.1
mypy==1.11.1
# via
# -r requirements/edx/development.in
# django-stubs
@@ -1221,7 +1290,7 @@ mysqlclient==2.2.4
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
newrelic==9.12.0
newrelic==9.13.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -1230,7 +1299,7 @@ nh3==0.2.18
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
nltk==3.8.1
nltk==3.9.1
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -1303,7 +1372,7 @@ openedx-filters==1.9.0
# -r requirements/edx/testing.txt
# lti-consumer-xblock
# ora2
openedx-learning==0.10.0
openedx-learning==0.10.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/doc.txt
@@ -1317,7 +1386,7 @@ optimizely-sdk==4.1.1
# -c requirements/edx/../constraints.txt
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
ora2==6.11.1
ora2==6.11.2
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -1336,14 +1405,14 @@ packaging==24.1
# snowflake-connector-python
# sphinx
# tox
pact-python==2.0.1
pact-python==2.2.1
# via -r requirements/edx/testing.txt
pansi==2020.7.3
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# py2neo
paramiko==3.4.0
paramiko==3.4.1
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -1417,6 +1486,21 @@ prompt-toolkit==3.0.47
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# click-repl
proto-plus==1.24.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# google-api-core
# google-cloud-firestore
protobuf==5.27.3
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# google-api-core
# google-cloud-firestore
# googleapis-common-protos
# grpcio-status
# proto-plus
psutil==6.0.0
# via
# -r requirements/edx/doc.txt
@@ -1436,6 +1520,13 @@ pyasn1==0.6.0
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# pgpy
# pyasn1-modules
# rsa
pyasn1-modules==0.4.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# google-auth
pycodestyle==2.8.0
# via
# -c requirements/edx/../constraints.txt
@@ -1479,7 +1570,6 @@ pygments==2.18.0
# diff-cover
# py2neo
# pydata-sphinx-theme
# rich
# sphinx
# sphinx-mdinclude
pyjwkest==1.4.2
@@ -1488,7 +1578,7 @@ pyjwkest==1.4.2
# -r requirements/edx/testing.txt
# edx-token-utils
# lti-consumer-xblock
pyjwt[crypto]==2.8.0
pyjwt[crypto]==2.9.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -1497,6 +1587,7 @@ pyjwt[crypto]==2.8.0
# edx-drf-extensions
# edx-proctoring
# edx-rest-api-client
# firebase-admin
# pylti1p3
# snowflake-connector-python
# social-auth-core
@@ -1556,7 +1647,7 @@ pynliner==0.8.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
pyopenssl==24.1.0
pyopenssl==24.2.1
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -1567,6 +1658,7 @@ pyparsing==3.1.2
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# chem
# httplib2
# openedx-calc
pyproject-api==1.7.1
# via
@@ -1589,7 +1681,7 @@ pysrt==1.1.2
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# edxval
pytest==8.2.2
pytest==8.3.2
# via
# -r requirements/edx/testing.txt
# pylint-pytest
@@ -1632,10 +1724,6 @@ python-dateutil==2.9.0.post0
# olxcleaner
# ora2
# xblock
python-dotenv==1.0.1
# via
# -r requirements/edx/testing.txt
# uvicorn
python-ipware==3.0.0
# via
# -r requirements/edx/doc.txt
@@ -1645,10 +1733,6 @@ python-memcached==1.62
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
python-multipart==0.0.9
# via
# -r requirements/edx/testing.txt
# fastapi
python-slugify==8.0.4
# via
# -r requirements/edx/doc.txt
@@ -1694,7 +1778,7 @@ pyuca==1.2
# -r requirements/edx/testing.txt
pywatchman==2.0.0
# via -r requirements/edx/development.in
pyyaml==6.0.1
pyyaml==6.0.2
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -1705,7 +1789,6 @@ pyyaml==6.0.1
# edx-i18n-tools
# jsondiff
# sphinxcontrib-openapi
# uvicorn
# xblock
random2==1.0.2
# via
@@ -1715,7 +1798,7 @@ recommender-xblock==2.2.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
redis==5.0.7
redis==5.0.8
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -1726,7 +1809,7 @@ referencing==0.35.1
# -r requirements/edx/testing.txt
# jsonschema
# jsonschema-specifications
regex==2024.5.15
regex==2024.7.24
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -1737,6 +1820,7 @@ requests==2.32.3
# -r requirements/edx/testing.txt
# algoliasearch
# analytics-python
# cachecontrol
# django-oauth-toolkit
# djangorestframework-stubs
# edx-bulk-grades
@@ -1744,6 +1828,8 @@ requests==2.32.3
# edx-enterprise
# edx-rest-api-client
# geoip2
# google-api-core
# google-cloud-storage
# mailsnake
# meilisearch
# openai
@@ -1764,20 +1850,17 @@ requests-oauthlib==2.0.0
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# social-auth-core
rfc3986[idna2008]==1.5.0
# via
# -r requirements/edx/testing.txt
# httpx
rich==13.7.1
# via
# -r requirements/edx/testing.txt
# typer
rpds-py==0.19.0
rpds-py==0.20.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# jsonschema
# referencing
rsa==4.9
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# google-auth
rules==3.4
# via
# -r requirements/edx/doc.txt
@@ -1806,15 +1889,11 @@ semantic-version==2.10.0
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# edx-drf-extensions
shapely==2.0.5
shapely==2.0.6
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
shellingham==1.5.4
# via
# -r requirements/edx/testing.txt
# typer
simplejson==3.19.2
simplejson==3.19.3
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -1871,19 +1950,18 @@ sniffio==1.3.1
# via
# -r requirements/edx/testing.txt
# anyio
# httpcore
# httpx
snowballstemmer==2.2.0
# via
# -r requirements/edx/doc.txt
# sphinx
snowflake-connector-python==3.11.0
snowflake-connector-python==3.12.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# edx-enterprise
social-auth-app-django==5.4.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# edx-auth-backends
@@ -1903,12 +1981,12 @@ sortedcontainers==2.4.0
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# snowflake-connector-python
soupsieve==2.5
soupsieve==2.6
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# beautifulsoup4
sphinx==7.4.4
sphinx==8.0.2
# via
# -r requirements/edx/doc.txt
# pydata-sphinx-theme
@@ -1921,23 +1999,23 @@ sphinx==7.4.4
# sphinxext-rediraffe
sphinx-book-theme==1.1.3
# via -r requirements/edx/doc.txt
sphinx-design==0.6.0
sphinx-design==0.6.1
# via -r requirements/edx/doc.txt
sphinx-mdinclude==0.6.1
sphinx-mdinclude==0.6.2
# via
# -r requirements/edx/doc.txt
# sphinxcontrib-openapi
sphinx-reredirects==0.1.5
# via -r requirements/edx/doc.txt
sphinxcontrib-applehelp==1.0.8
sphinxcontrib-applehelp==2.0.0
# via
# -r requirements/edx/doc.txt
# sphinx
sphinxcontrib-devhelp==1.0.6
sphinxcontrib-devhelp==2.0.0
# via
# -r requirements/edx/doc.txt
# sphinx
sphinxcontrib-htmlhelp==2.0.5
sphinxcontrib-htmlhelp==2.1.0
# via
# -r requirements/edx/doc.txt
# sphinx
@@ -1951,11 +2029,11 @@ sphinxcontrib-jsmath==1.0.1
# sphinx
sphinxcontrib-openapi[markdown]==0.8.4
# via -r requirements/edx/doc.txt
sphinxcontrib-qthelp==1.0.7
sphinxcontrib-qthelp==2.0.0
# via
# -r requirements/edx/doc.txt
# sphinx
sphinxcontrib-serializinghtml==1.1.10
sphinxcontrib-serializinghtml==2.0.0
# via
# -r requirements/edx/doc.txt
# sphinx
@@ -1971,7 +2049,7 @@ staff-graded-xblock==2.3.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
starlette==0.37.2
starlette==0.38.2
# via
# -r requirements/edx/testing.txt
# fastapi
@@ -1989,7 +2067,7 @@ super-csv==3.2.0
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# edx-bulk-grades
sympy==1.13.0
sympy==1.13.2
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
@@ -2011,27 +2089,23 @@ tinycss2==1.2.1
# bleach
tomli==2.0.1
# via django-stubs
tomlkit==0.13.0
tomlkit==0.13.2
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# pylint
# snowflake-connector-python
tox==4.16.0
tox==4.18.0
# via -r requirements/edx/testing.txt
tqdm==4.66.4
tqdm==4.66.5
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# nltk
# openai
typer==0.12.3
# via
# -r requirements/edx/testing.txt
# fastapi-cli
types-pytz==2024.1.0.20240417
# via django-stubs
types-pyyaml==6.0.12.20240311
types-pyyaml==6.0.12.20240808
# via
# django-stubs
# djangorestframework-stubs
@@ -2058,7 +2132,6 @@ typing-extensions==4.12.2
# pydata-sphinx-theme
# pylti1p3
# snowflake-connector-python
# typer
tzdata==2024.1
# via
# -r requirements/edx/doc.txt
@@ -2077,6 +2150,7 @@ uritemplate==4.1.1
# -r requirements/edx/testing.txt
# drf-spectacular
# drf-yasg
# google-api-python-client
urllib3==1.26.19
# via
# -c requirements/edx/../constraints.txt
@@ -2084,22 +2158,16 @@ urllib3==1.26.19
# -r requirements/edx/testing.txt
# botocore
# elasticsearch
# pact-python
# py2neo
# requests
user-util==1.1.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
uvicorn[standard]==0.30.1
uvicorn==0.30.6
# via
# -r requirements/edx/testing.txt
# fastapi
# pact-python
uvloop==0.19.0
# via
# -r requirements/edx/testing.txt
# uvicorn
vine==5.1.0
# via
# -r requirements/edx/doc.txt
@@ -2123,15 +2191,11 @@ walrus==0.9.4
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# edx-event-bus-redis
watchdog==4.0.1
watchdog==4.0.2
# via
# -r requirements/edx/development.in
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
watchfiles==0.22.0
# via
# -r requirements/edx/testing.txt
# uvicorn
wcwidth==0.2.13
# via
# -r requirements/edx/doc.txt
@@ -2153,16 +2217,12 @@ webencodings==0.5.1
# bleach
# html5lib
# tinycss2
webob==1.8.7
webob==1.8.8
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# xblock
websockets==12.0
# via
# -r requirements/edx/testing.txt
# uvicorn
wheel==0.43.0
wheel==0.44.0
# via
# -r requirements/edx/../pip-tools.txt
# pip-tools
@@ -2173,6 +2233,7 @@ wrapt==1.16.0
# astroid
xblock[django]==4.0.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# acid-xblock
@@ -2220,7 +2281,8 @@ yarl==1.9.4
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
# aiohttp
zipp==3.19.2
# pact-python
zipp==3.20.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt

View File

@@ -10,7 +10,11 @@ accessible-pygments==0.0.5
# via pydata-sphinx-theme
acid-xblock==0.3.1
# via -r requirements/edx/base.txt
aiohttp==3.9.5
aiohappyeyeballs==2.4.0
# via
# -r requirements/edx/base.txt
# aiohttp
aiohttp==3.10.5
# via
# -r requirements/edx/base.txt
# geoip2
@@ -19,10 +23,12 @@ aiosignal==1.3.1
# via
# -r requirements/edx/base.txt
# aiohttp
alabaster==0.7.16
alabaster==1.0.0
# via sphinx
algoliasearch==3.0.0
# via -r requirements/edx/base.txt
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
amqp==5.2.0
# via
# -r requirements/edx/base.txt
@@ -51,7 +57,7 @@ asn1crypto==1.5.1
# via
# -r requirements/edx/base.txt
# snowflake-connector-python
attrs==23.2.0
attrs==24.2.0
# via
# -r requirements/edx/base.txt
# aiohttp
@@ -61,7 +67,7 @@ attrs==23.2.0
# openedx-events
# openedx-learning
# referencing
babel==2.15.0
babel==2.16.0
# via
# -r requirements/edx/base.txt
# enmerkar
@@ -72,7 +78,7 @@ backoff==1.10.0
# via
# -r requirements/edx/base.txt
# analytics-python
bcrypt==4.1.3
bcrypt==4.2.0
# via
# -r requirements/edx/base.txt
# paramiko
@@ -96,19 +102,27 @@ bleach[css]==6.1.0
# xblock-poll
boto==2.49.0
# via -r requirements/edx/base.txt
boto3==1.34.144
boto3==1.35.1
# via
# -r requirements/edx/base.txt
# django-ses
# fs-s3fs
# ora2
botocore==1.34.144
botocore==1.35.1
# via
# -r requirements/edx/base.txt
# boto3
# s3transfer
bridgekeeper==0.9
# via -r requirements/edx/base.txt
cachecontrol==0.14.0
# via
# -r requirements/edx/base.txt
# firebase-admin
cachetools==5.5.0
# via
# -r requirements/edx/base.txt
# google-auth
camel-converter[pydantic]==3.1.2
# via
# -r requirements/edx/base.txt
@@ -130,7 +144,7 @@ certifi==2024.7.4
# py2neo
# requests
# snowflake-connector-python
cffi==1.16.0
cffi==1.17.0
# via
# -r requirements/edx/base.txt
# cryptography
@@ -208,7 +222,7 @@ defusedxml==0.7.1
# ora2
# python3-openid
# social-auth-core
django==4.2.14
django==4.2.15
# via
# -c requirements/edx/../common_constraints.txt
# -c requirements/edx/../constraints.txt
@@ -227,6 +241,7 @@ django==4.2.14
# django-multi-email-field
# django-mysql
# django-oauth-toolkit
# django-push-notifications
# django-sekizai
# django-ses
# django-statici18n
@@ -318,7 +333,7 @@ django-fernet-fields-v2==0.9
# via
# -r requirements/edx/base.txt
# edx-enterprise
django-filter==24.2
django-filter==24.3
# via
# -r requirements/edx/base.txt
# edx-enterprise
@@ -373,6 +388,10 @@ django-object-actions==4.2.0
# edx-enterprise
django-pipeline==3.1.0
# via -r requirements/edx/base.txt
django-push-notifications==3.1.0
# via
# -r requirements/edx/base.txt
# edx-ace
django-ratelimit==4.1.0
# via -r requirements/edx/base.txt
django-sekizai==4.1.0
@@ -462,7 +481,7 @@ drf-yasg==1.21.7
# -r requirements/edx/base.txt
# django-user-tasks
# edx-api-doc-tools
edx-ace==1.9.1
edx-ace==1.11.1
# via -r requirements/edx/base.txt
edx-api-doc-tools==1.8.0
# via
@@ -490,7 +509,7 @@ edx-celeryutils==1.3.0
# super-csv
edx-codejail==3.4.1
# via -r requirements/edx/base.txt
edx-completion==4.6.6
edx-completion==4.6.7
# via -r requirements/edx/base.txt
edx-django-release-util==1.4.0
# via
@@ -499,7 +518,7 @@ edx-django-release-util==1.4.0
# edxval
edx-django-sites-extensions==4.2.0
# via -r requirements/edx/base.txt
edx-django-utils==5.14.2
edx-django-utils==5.15.0
# via
# -r requirements/edx/base.txt
# django-config-models
@@ -528,11 +547,11 @@ edx-drf-extensions==10.3.0
# edx-when
# edxval
# openedx-learning
edx-enterprise==4.21.9
edx-enterprise==4.23.9
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
edx-event-bus-kafka==5.7.0
edx-event-bus-kafka==5.8.1
# via -r requirements/edx/base.txt
edx-event-bus-redis==0.5.0
# via -r requirements/edx/base.txt
@@ -575,11 +594,11 @@ edx-rest-api-client==5.7.1
# -r requirements/edx/base.txt
# edx-enterprise
# edx-proctoring
edx-search==3.9.1
edx-search==4.0.0
# via -r requirements/edx/base.txt
edx-sga==0.25.0
# via -r requirements/edx/base.txt
edx-submissions==3.7.5
edx-submissions==3.7.7
# via
# -r requirements/edx/base.txt
# ora2
@@ -633,6 +652,10 @@ filelock==3.15.4
# via
# -r requirements/edx/base.txt
# snowflake-connector-python
firebase-admin==6.5.0
# via
# -r requirements/edx/base.txt
# edx-ace
frozenlist==1.4.1
# via
# -r requirements/edx/base.txt
@@ -660,7 +683,68 @@ gitpython==3.1.43
# via -r requirements/edx/doc.in
glob2==0.7
# via -r requirements/edx/base.txt
gunicorn==22.0.0
google-api-core[grpc]==2.19.1
# via
# -r requirements/edx/base.txt
# firebase-admin
# google-api-python-client
# google-cloud-core
# google-cloud-firestore
# google-cloud-storage
google-api-python-client==2.141.0
# via
# -r requirements/edx/base.txt
# firebase-admin
google-auth==2.34.0
# via
# -r requirements/edx/base.txt
# google-api-core
# google-api-python-client
# google-auth-httplib2
# google-cloud-core
# google-cloud-firestore
# google-cloud-storage
google-auth-httplib2==0.2.0
# via
# -r requirements/edx/base.txt
# google-api-python-client
google-cloud-core==2.4.1
# via
# -r requirements/edx/base.txt
# google-cloud-firestore
# google-cloud-storage
google-cloud-firestore==2.17.2
# via
# -r requirements/edx/base.txt
# firebase-admin
google-cloud-storage==2.18.2
# via
# -r requirements/edx/base.txt
# firebase-admin
google-crc32c==1.5.0
# via
# -r requirements/edx/base.txt
# google-cloud-storage
# google-resumable-media
google-resumable-media==2.7.2
# via
# -r requirements/edx/base.txt
# google-cloud-storage
googleapis-common-protos==1.63.2
# via
# -r requirements/edx/base.txt
# google-api-core
# grpcio-status
grpcio==1.65.5
# via
# -r requirements/edx/base.txt
# google-api-core
# grpcio-status
grpcio-status==1.65.5
# via
# -r requirements/edx/base.txt
# google-api-core
gunicorn==23.0.0
# via -r requirements/edx/base.txt
help-tokens==2.4.0
# via -r requirements/edx/base.txt
@@ -668,6 +752,11 @@ html5lib==1.1
# via
# -r requirements/edx/base.txt
# ora2
httplib2==0.22.0
# via
# -r requirements/edx/base.txt
# google-api-python-client
# google-auth-httplib2
icalendar==5.0.13
# via -r requirements/edx/base.txt
idna==3.7
@@ -679,10 +768,8 @@ idna==3.7
# yarl
imagesize==1.4.1
# via sphinx
importlib-metadata==6.11.0
# via
# -c requirements/edx/../common_constraints.txt
# -r requirements/edx/base.txt
importlib-metadata==8.3.0
# via -r requirements/edx/base.txt
inflection==0.5.1
# via
# -r requirements/edx/base.txt
@@ -712,7 +799,7 @@ joblib==1.4.2
# via
# -r requirements/edx/base.txt
# nltk
jsondiff==2.1.2
jsondiff==2.2.0
# via
# -r requirements/edx/base.txt
# edx-enterprise
@@ -740,7 +827,7 @@ jwcrypto==1.5.6
# -r requirements/edx/base.txt
# django-oauth-toolkit
# pylti1p3
kombu==5.3.7
kombu==5.4.0
# via
# -r requirements/edx/base.txt
# celery
@@ -817,7 +904,7 @@ monotonic==1.6
# -r requirements/edx/base.txt
# analytics-python
# py2neo
more-itertools==10.3.0
more-itertools==10.4.0
# via
# -r requirements/edx/base.txt
# cssutils
@@ -825,6 +912,10 @@ mpmath==1.3.0
# via
# -r requirements/edx/base.txt
# sympy
msgpack==1.0.8
# via
# -r requirements/edx/base.txt
# cachecontrol
multidict==6.0.5
# via
# -r requirements/edx/base.txt
@@ -832,13 +923,13 @@ multidict==6.0.5
# yarl
mysqlclient==2.2.4
# via -r requirements/edx/base.txt
newrelic==9.12.0
newrelic==9.13.0
# via
# -r requirements/edx/base.txt
# edx-django-utils
nh3==0.2.18
# via -r requirements/edx/base.txt
nltk==3.8.1
nltk==3.9.1
# via
# -r requirements/edx/base.txt
# chem
@@ -891,7 +982,7 @@ openedx-filters==1.9.0
# -r requirements/edx/base.txt
# lti-consumer-xblock
# ora2
openedx-learning==0.10.0
openedx-learning==0.10.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
@@ -901,7 +992,7 @@ optimizely-sdk==4.1.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
ora2==6.11.1
ora2==6.11.2
# via -r requirements/edx/base.txt
packaging==24.1
# via
@@ -916,7 +1007,7 @@ pansi==2020.7.3
# via
# -r requirements/edx/base.txt
# py2neo
paramiko==3.4.0
paramiko==3.4.1
# via
# -r requirements/edx/base.txt
# edx-enterprise
@@ -964,6 +1055,19 @@ prompt-toolkit==3.0.47
# via
# -r requirements/edx/base.txt
# click-repl
proto-plus==1.24.0
# via
# -r requirements/edx/base.txt
# google-api-core
# google-cloud-firestore
protobuf==5.27.3
# via
# -r requirements/edx/base.txt
# google-api-core
# google-cloud-firestore
# googleapis-common-protos
# grpcio-status
# proto-plus
psutil==6.0.0
# via
# -r requirements/edx/base.txt
@@ -976,6 +1080,12 @@ pyasn1==0.6.0
# via
# -r requirements/edx/base.txt
# pgpy
# pyasn1-modules
# rsa
pyasn1-modules==0.4.0
# via
# -r requirements/edx/base.txt
# google-auth
pycountry==24.6.1
# via -r requirements/edx/base.txt
pycparser==2.22
@@ -1011,7 +1121,7 @@ pyjwkest==1.4.2
# -r requirements/edx/base.txt
# edx-token-utils
# lti-consumer-xblock
pyjwt[crypto]==2.8.0
pyjwt[crypto]==2.9.0
# via
# -r requirements/edx/base.txt
# drf-jwt
@@ -1019,6 +1129,7 @@ pyjwt[crypto]==2.8.0
# edx-drf-extensions
# edx-proctoring
# edx-rest-api-client
# firebase-admin
# pylti1p3
# snowflake-connector-python
# social-auth-core
@@ -1045,7 +1156,7 @@ pynacl==1.5.0
# paramiko
pynliner==0.8.0
# via -r requirements/edx/base.txt
pyopenssl==24.1.0
pyopenssl==24.2.1
# via
# -r requirements/edx/base.txt
# optimizely-sdk
@@ -1054,6 +1165,7 @@ pyparsing==3.1.2
# via
# -r requirements/edx/base.txt
# chem
# httplib2
# openedx-calc
pyrsistent==0.20.0
# via
@@ -1117,7 +1229,7 @@ pytz==2024.1
# xblock
pyuca==1.2
# via -r requirements/edx/base.txt
pyyaml==6.0.1
pyyaml==6.0.2
# via
# -r requirements/edx/base.txt
# code-annotations
@@ -1132,7 +1244,7 @@ random2==1.0.2
# via -r requirements/edx/base.txt
recommender-xblock==2.2.0
# via -r requirements/edx/base.txt
redis==5.0.7
redis==5.0.8
# via
# -r requirements/edx/base.txt
# walrus
@@ -1141,7 +1253,7 @@ referencing==0.35.1
# -r requirements/edx/base.txt
# jsonschema
# jsonschema-specifications
regex==2024.5.15
regex==2024.7.24
# via
# -r requirements/edx/base.txt
# nltk
@@ -1150,12 +1262,15 @@ requests==2.32.3
# -r requirements/edx/base.txt
# algoliasearch
# analytics-python
# cachecontrol
# django-oauth-toolkit
# edx-bulk-grades
# edx-drf-extensions
# edx-enterprise
# edx-rest-api-client
# geoip2
# google-api-core
# google-cloud-storage
# mailsnake
# meilisearch
# openai
@@ -1174,11 +1289,15 @@ requests-oauthlib==2.0.0
# via
# -r requirements/edx/base.txt
# social-auth-core
rpds-py==0.19.0
rpds-py==0.20.0
# via
# -r requirements/edx/base.txt
# jsonschema
# referencing
rsa==4.9
# via
# -r requirements/edx/base.txt
# google-auth
rules==3.4
# via
# -r requirements/edx/base.txt
@@ -1202,9 +1321,9 @@ semantic-version==2.10.0
# via
# -r requirements/edx/base.txt
# edx-drf-extensions
shapely==2.0.5
shapely==2.0.6
# via -r requirements/edx/base.txt
simplejson==3.19.2
simplejson==3.19.3
# via
# -r requirements/edx/base.txt
# sailthru-client
@@ -1249,12 +1368,13 @@ smmap==5.0.1
# via gitdb
snowballstemmer==2.2.0
# via sphinx
snowflake-connector-python==3.11.0
snowflake-connector-python==3.12.0
# via
# -r requirements/edx/base.txt
# edx-enterprise
social-auth-app-django==5.4.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
# edx-auth-backends
social-auth-core==4.5.4
@@ -1270,11 +1390,11 @@ sortedcontainers==2.4.0
# via
# -r requirements/edx/base.txt
# snowflake-connector-python
soupsieve==2.5
soupsieve==2.6
# via
# -r requirements/edx/base.txt
# beautifulsoup4
sphinx==7.4.4
sphinx==8.0.2
# via
# -r requirements/edx/doc.in
# pydata-sphinx-theme
@@ -1287,17 +1407,17 @@ sphinx==7.4.4
# sphinxext-rediraffe
sphinx-book-theme==1.1.3
# via -r requirements/edx/doc.in
sphinx-design==0.6.0
sphinx-design==0.6.1
# via -r requirements/edx/doc.in
sphinx-mdinclude==0.6.1
sphinx-mdinclude==0.6.2
# via sphinxcontrib-openapi
sphinx-reredirects==0.1.5
# via -r requirements/edx/doc.in
sphinxcontrib-applehelp==1.0.8
sphinxcontrib-applehelp==2.0.0
# via sphinx
sphinxcontrib-devhelp==1.0.6
sphinxcontrib-devhelp==2.0.0
# via sphinx
sphinxcontrib-htmlhelp==2.0.5
sphinxcontrib-htmlhelp==2.1.0
# via sphinx
sphinxcontrib-httpdomain==1.8.1
# via sphinxcontrib-openapi
@@ -1305,9 +1425,9 @@ sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-openapi[markdown]==0.8.4
# via -r requirements/edx/doc.in
sphinxcontrib-qthelp==1.0.7
sphinxcontrib-qthelp==2.0.0
# via sphinx
sphinxcontrib-serializinghtml==1.1.10
sphinxcontrib-serializinghtml==2.0.0
# via sphinx
sphinxext-rediraffe==0.2.7
# via -r requirements/edx/doc.in
@@ -1329,7 +1449,7 @@ super-csv==3.2.0
# via
# -r requirements/edx/base.txt
# edx-bulk-grades
sympy==1.13.0
sympy==1.13.2
# via
# -r requirements/edx/base.txt
# openedx-calc
@@ -1345,11 +1465,11 @@ tinycss2==1.2.1
# via
# -r requirements/edx/base.txt
# bleach
tomlkit==0.13.0
tomlkit==0.13.2
# via
# -r requirements/edx/base.txt
# snowflake-connector-python
tqdm==4.66.4
tqdm==4.66.5
# via
# -r requirements/edx/base.txt
# nltk
@@ -1378,6 +1498,7 @@ uritemplate==4.1.1
# -r requirements/edx/base.txt
# drf-spectacular
# drf-yasg
# google-api-python-client
urllib3==1.26.19
# via
# -c requirements/edx/../constraints.txt
@@ -1402,7 +1523,7 @@ walrus==0.9.4
# via
# -r requirements/edx/base.txt
# edx-event-bus-redis
watchdog==4.0.1
watchdog==4.0.2
# via -r requirements/edx/base.txt
wcwidth==0.2.13
# via
@@ -1422,7 +1543,7 @@ webencodings==0.5.1
# bleach
# html5lib
# tinycss2
webob==1.8.7
webob==1.8.8
# via
# -r requirements/edx/base.txt
# xblock
@@ -1430,6 +1551,7 @@ wrapt==1.16.0
# via -r requirements/edx/base.txt
xblock[django]==4.0.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
# acid-xblock
# crowdsourcehinter-xblock
@@ -1465,7 +1587,7 @@ yarl==1.9.4
# via
# -r requirements/edx/base.txt
# aiohttp
zipp==3.19.2
zipp==3.20.0
# via
# -r requirements/edx/base.txt
# importlib-metadata

View File

@@ -61,7 +61,7 @@ urllib3==1.26.19
# via
# -c requirements/edx/../constraints.txt
# requests
watchdog==4.0.1
watchdog==4.0.2
# via -r requirements/edx/paver.in
wrapt==1.16.0
# via -r requirements/edx/paver.in

View File

@@ -4,7 +4,7 @@
#
# make upgrade
#
attrs==23.2.0
attrs==24.2.0
# via
# glom
# jsonschema
@@ -15,7 +15,7 @@ boltons==21.0.0
# face
# glom
# semgrep
bracex==2.4
bracex==2.5
# via wcmatch
certifi==2024.7.4
# via requests
@@ -62,7 +62,7 @@ requests==2.32.3
# via semgrep
rich==13.7.1
# via semgrep
rpds-py==0.19.0
rpds-py==0.20.0
# via
# jsonschema
# referencing

View File

@@ -8,7 +8,11 @@
# via -r requirements/edx/base.txt
acid-xblock==0.3.1
# via -r requirements/edx/base.txt
aiohttp==3.9.5
aiohappyeyeballs==2.4.0
# via
# -r requirements/edx/base.txt
# aiohttp
aiohttp==3.10.5
# via
# -r requirements/edx/base.txt
# geoip2
@@ -18,7 +22,9 @@ aiosignal==1.3.1
# -r requirements/edx/base.txt
# aiohttp
algoliasearch==3.0.0
# via -r requirements/edx/base.txt
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
amqp==5.2.0
# via
# -r requirements/edx/base.txt
@@ -34,10 +40,7 @@ annotated-types==0.7.0
# -r requirements/edx/base.txt
# pydantic
anyio==4.4.0
# via
# httpcore
# starlette
# watchfiles
# via starlette
appdirs==1.4.4
# via
# -r requirements/edx/base.txt
@@ -56,7 +59,7 @@ astroid==2.13.5
# via
# pylint
# pylint-celery
attrs==23.2.0
attrs==24.2.0
# via
# -r requirements/edx/base.txt
# aiohttp
@@ -66,7 +69,7 @@ attrs==23.2.0
# openedx-events
# openedx-learning
# referencing
babel==2.15.0
babel==2.16.0
# via
# -r requirements/edx/base.txt
# enmerkar
@@ -75,7 +78,7 @@ backoff==1.10.0
# via
# -r requirements/edx/base.txt
# analytics-python
bcrypt==4.1.3
bcrypt==4.2.0
# via
# -r requirements/edx/base.txt
# paramiko
@@ -99,21 +102,28 @@ bleach[css]==6.1.0
# xblock-poll
boto==2.49.0
# via -r requirements/edx/base.txt
boto3==1.34.144
boto3==1.35.1
# via
# -r requirements/edx/base.txt
# django-ses
# fs-s3fs
# ora2
botocore==1.34.144
botocore==1.35.1
# via
# -r requirements/edx/base.txt
# boto3
# s3transfer
bridgekeeper==0.9
# via -r requirements/edx/base.txt
cachetools==5.4.0
# via tox
cachecontrol==0.14.0
# via
# -r requirements/edx/base.txt
# firebase-admin
cachetools==5.5.0
# via
# -r requirements/edx/base.txt
# google-auth
# tox
camel-converter[pydantic]==3.1.2
# via
# -r requirements/edx/base.txt
@@ -132,15 +142,14 @@ certifi==2024.7.4
# via
# -r requirements/edx/base.txt
# elasticsearch
# httpcore
# httpx
# py2neo
# requests
# snowflake-connector-python
cffi==1.16.0
cffi==1.17.0
# via
# -r requirements/edx/base.txt
# cryptography
# pact-python
# pynacl
# snowflake-connector-python
chardet==5.2.0
@@ -173,7 +182,6 @@ click==8.1.6
# import-linter
# nltk
# pact-python
# typer
# user-util
# uvicorn
click-didyoumean==0.3.1
@@ -201,7 +209,7 @@ codejail-includes==1.0.0
# via -r requirements/edx/base.txt
colorama==0.4.6
# via tox
coverage[toml]==7.6.0
coverage[toml]==7.6.1
# via
# -r requirements/edx/coverage.txt
# pytest-cov
@@ -237,13 +245,13 @@ defusedxml==0.7.1
# ora2
# python3-openid
# social-auth-core
diff-cover==9.1.0
diff-cover==9.1.1
# via -r requirements/edx/coverage.txt
dill==0.3.8
# via pylint
distlib==0.3.8
# via virtualenv
django==4.2.14
django==4.2.15
# via
# -c requirements/edx/../common_constraints.txt
# -c requirements/edx/../constraints.txt
@@ -262,6 +270,7 @@ django==4.2.14
# django-multi-email-field
# django-mysql
# django-oauth-toolkit
# django-push-notifications
# django-sekizai
# django-ses
# django-statici18n
@@ -353,7 +362,7 @@ django-fernet-fields-v2==0.9
# via
# -r requirements/edx/base.txt
# edx-enterprise
django-filter==24.2
django-filter==24.3
# via
# -r requirements/edx/base.txt
# edx-enterprise
@@ -408,6 +417,10 @@ django-object-actions==4.2.0
# edx-enterprise
django-pipeline==3.1.0
# via -r requirements/edx/base.txt
django-push-notifications==3.1.0
# via
# -r requirements/edx/base.txt
# edx-ace
django-ratelimit==4.1.0
# via -r requirements/edx/base.txt
django-sekizai==4.1.0
@@ -478,7 +491,6 @@ djangorestframework-xml==2.0.0
dnspython==2.6.1
# via
# -r requirements/edx/base.txt
# email-validator
# pymongo
done-xblock==2.3.0
# via -r requirements/edx/base.txt
@@ -493,7 +505,7 @@ drf-yasg==1.21.7
# -r requirements/edx/base.txt
# django-user-tasks
# edx-api-doc-tools
edx-ace==1.9.1
edx-ace==1.11.1
# via -r requirements/edx/base.txt
edx-api-doc-tools==1.8.0
# via
@@ -521,7 +533,7 @@ edx-celeryutils==1.3.0
# super-csv
edx-codejail==3.4.1
# via -r requirements/edx/base.txt
edx-completion==4.6.6
edx-completion==4.6.7
# via -r requirements/edx/base.txt
edx-django-release-util==1.4.0
# via
@@ -530,7 +542,7 @@ edx-django-release-util==1.4.0
# edxval
edx-django-sites-extensions==4.2.0
# via -r requirements/edx/base.txt
edx-django-utils==5.14.2
edx-django-utils==5.15.0
# via
# -r requirements/edx/base.txt
# django-config-models
@@ -559,11 +571,11 @@ edx-drf-extensions==10.3.0
# edx-when
# edxval
# openedx-learning
edx-enterprise==4.21.9
edx-enterprise==4.23.9
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
edx-event-bus-kafka==5.7.0
edx-event-bus-kafka==5.8.1
# via -r requirements/edx/base.txt
edx-event-bus-redis==0.5.0
# via -r requirements/edx/base.txt
@@ -608,11 +620,11 @@ edx-rest-api-client==5.7.1
# -r requirements/edx/base.txt
# edx-enterprise
# edx-proctoring
edx-search==3.9.1
edx-search==4.0.0
# via -r requirements/edx/base.txt
edx-sga==0.25.0
# via -r requirements/edx/base.txt
edx-submissions==3.7.5
edx-submissions==3.7.7
# via
# -r requirements/edx/base.txt
# ora2
@@ -645,8 +657,6 @@ elasticsearch==7.13.4
# -c requirements/edx/../common_constraints.txt
# -r requirements/edx/base.txt
# edx-search
email-validator==2.2.0
# via fastapi
enmerkar==0.7.1
# via
# -r requirements/edx/base.txt
@@ -662,14 +672,12 @@ event-tracking==3.0.0
# edx-search
execnet==2.1.1
# via pytest-xdist
factory-boy==3.3.0
factory-boy==3.3.1
# via -r requirements/edx/testing.in
faker==26.0.0
faker==27.0.0
# via factory-boy
fastapi==0.111.1
fastapi==0.112.1
# via pact-python
fastapi-cli==0.0.4
# via fastapi
fastavro==1.9.5
# via
# -r requirements/edx/base.txt
@@ -680,6 +688,10 @@ filelock==3.15.4
# snowflake-connector-python
# tox
# virtualenv
firebase-admin==6.5.0
# via
# -r requirements/edx/base.txt
# edx-ace
freezegun==1.5.1
# via -r requirements/edx/testing.in
frozenlist==1.4.1
@@ -705,48 +717,100 @@ geoip2==4.8.0
# via -r requirements/edx/base.txt
glob2==0.7
# via -r requirements/edx/base.txt
google-api-core[grpc]==2.19.1
# via
# -r requirements/edx/base.txt
# firebase-admin
# google-api-python-client
# google-cloud-core
# google-cloud-firestore
# google-cloud-storage
google-api-python-client==2.141.0
# via
# -r requirements/edx/base.txt
# firebase-admin
google-auth==2.34.0
# via
# -r requirements/edx/base.txt
# google-api-core
# google-api-python-client
# google-auth-httplib2
# google-cloud-core
# google-cloud-firestore
# google-cloud-storage
google-auth-httplib2==0.2.0
# via
# -r requirements/edx/base.txt
# google-api-python-client
google-cloud-core==2.4.1
# via
# -r requirements/edx/base.txt
# google-cloud-firestore
# google-cloud-storage
google-cloud-firestore==2.17.2
# via
# -r requirements/edx/base.txt
# firebase-admin
google-cloud-storage==2.18.2
# via
# -r requirements/edx/base.txt
# firebase-admin
google-crc32c==1.5.0
# via
# -r requirements/edx/base.txt
# google-cloud-storage
# google-resumable-media
google-resumable-media==2.7.2
# via
# -r requirements/edx/base.txt
# google-cloud-storage
googleapis-common-protos==1.63.2
# via
# -r requirements/edx/base.txt
# google-api-core
# grpcio-status
grimp==3.4.1
# via import-linter
gunicorn==22.0.0
grpcio==1.65.5
# via
# -r requirements/edx/base.txt
# google-api-core
# grpcio-status
grpcio-status==1.65.5
# via
# -r requirements/edx/base.txt
# google-api-core
gunicorn==23.0.0
# via -r requirements/edx/base.txt
h11==0.14.0
# via
# httpcore
# uvicorn
# via uvicorn
help-tokens==2.4.0
# via -r requirements/edx/base.txt
html5lib==1.1
# via
# -r requirements/edx/base.txt
# ora2
httpcore==0.16.3
# via httpx
httplib2==0.22.0
# via
# -r requirements/edx/base.txt
# google-api-python-client
# google-auth-httplib2
httpretty==1.1.4
# via -r requirements/edx/testing.in
httptools==0.6.1
# via uvicorn
httpx==0.23.3
# via
# fastapi
# pact-python
icalendar==5.0.13
# via -r requirements/edx/base.txt
idna==3.7
# via
# -r requirements/edx/base.txt
# anyio
# email-validator
# optimizely-sdk
# requests
# rfc3986
# snowflake-connector-python
# yarl
import-linter==2.0
# via -r requirements/edx/testing.in
importlib-metadata==6.11.0
# via
# -c requirements/edx/../common_constraints.txt
# -r requirements/edx/base.txt
importlib-metadata==8.3.0
# via -r requirements/edx/base.txt
inflection==0.5.1
# via
# -r requirements/edx/base.txt
@@ -774,7 +838,6 @@ jinja2==3.1.4
# -r requirements/edx/coverage.txt
# code-annotations
# diff-cover
# fastapi
jmespath==1.0.1
# via
# -r requirements/edx/base.txt
@@ -784,7 +847,7 @@ joblib==1.4.2
# via
# -r requirements/edx/base.txt
# nltk
jsondiff==2.1.2
jsondiff==2.2.0
# via
# -r requirements/edx/base.txt
# edx-enterprise
@@ -811,7 +874,7 @@ jwcrypto==1.5.6
# -r requirements/edx/base.txt
# django-oauth-toolkit
# pylti1p3
kombu==5.3.7
kombu==5.4.0
# via
# -r requirements/edx/base.txt
# celery
@@ -866,8 +929,6 @@ markdown==3.3.7
# openedx-django-wiki
# staff-graded-xblock
# xblock-poll
markdown-it-py==3.0.0
# via rich
markupsafe==2.1.5
# via
# -r requirements/edx/base.txt
@@ -883,8 +944,6 @@ maxminddb==2.6.2
# geoip2
mccabe==0.7.0
# via pylint
mdurl==0.1.2
# via markdown-it-py
meilisearch==0.31.4
# via -r requirements/edx/base.txt
mock==5.1.0
@@ -896,7 +955,7 @@ monotonic==1.6
# -r requirements/edx/base.txt
# analytics-python
# py2neo
more-itertools==10.3.0
more-itertools==10.4.0
# via
# -r requirements/edx/base.txt
# cssutils
@@ -904,6 +963,10 @@ mpmath==1.3.0
# via
# -r requirements/edx/base.txt
# sympy
msgpack==1.0.8
# via
# -r requirements/edx/base.txt
# cachecontrol
multidict==6.0.5
# via
# -r requirements/edx/base.txt
@@ -911,13 +974,13 @@ multidict==6.0.5
# yarl
mysqlclient==2.2.4
# via -r requirements/edx/base.txt
newrelic==9.12.0
newrelic==9.13.0
# via
# -r requirements/edx/base.txt
# edx-django-utils
nh3==0.2.18
# via -r requirements/edx/base.txt
nltk==3.8.1
nltk==3.9.1
# via
# -r requirements/edx/base.txt
# chem
@@ -970,7 +1033,7 @@ openedx-filters==1.9.0
# -r requirements/edx/base.txt
# lti-consumer-xblock
# ora2
openedx-learning==0.10.0
openedx-learning==0.10.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
@@ -980,7 +1043,7 @@ optimizely-sdk==4.1.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
ora2==6.11.1
ora2==6.11.2
# via -r requirements/edx/base.txt
packaging==24.1
# via
@@ -992,13 +1055,13 @@ packaging==24.1
# pytest
# snowflake-connector-python
# tox
pact-python==2.0.1
pact-python==2.2.1
# via -r requirements/edx/testing.in
pansi==2020.7.3
# via
# -r requirements/edx/base.txt
# py2neo
paramiko==3.4.0
paramiko==3.4.1
# via
# -r requirements/edx/base.txt
# edx-enterprise
@@ -1054,6 +1117,19 @@ prompt-toolkit==3.0.47
# via
# -r requirements/edx/base.txt
# click-repl
proto-plus==1.24.0
# via
# -r requirements/edx/base.txt
# google-api-core
# google-cloud-firestore
protobuf==5.27.3
# via
# -r requirements/edx/base.txt
# google-api-core
# google-cloud-firestore
# googleapis-common-protos
# grpcio-status
# proto-plus
psutil==6.0.0
# via
# -r requirements/edx/base.txt
@@ -1070,6 +1146,12 @@ pyasn1==0.6.0
# via
# -r requirements/edx/base.txt
# pgpy
# pyasn1-modules
# rsa
pyasn1-modules==0.4.0
# via
# -r requirements/edx/base.txt
# google-auth
pycodestyle==2.8.0
# via
# -c requirements/edx/../constraints.txt
@@ -1101,13 +1183,12 @@ pygments==2.18.0
# -r requirements/edx/coverage.txt
# diff-cover
# py2neo
# rich
pyjwkest==1.4.2
# via
# -r requirements/edx/base.txt
# edx-token-utils
# lti-consumer-xblock
pyjwt[crypto]==2.8.0
pyjwt[crypto]==2.9.0
# via
# -r requirements/edx/base.txt
# drf-jwt
@@ -1115,6 +1196,7 @@ pyjwt[crypto]==2.8.0
# edx-drf-extensions
# edx-proctoring
# edx-rest-api-client
# firebase-admin
# pylti1p3
# snowflake-connector-python
# social-auth-core
@@ -1159,7 +1241,7 @@ pynacl==1.5.0
# paramiko
pynliner==0.8.0
# via -r requirements/edx/base.txt
pyopenssl==24.1.0
pyopenssl==24.2.1
# via
# -r requirements/edx/base.txt
# optimizely-sdk
@@ -1168,6 +1250,7 @@ pyparsing==3.1.2
# via
# -r requirements/edx/base.txt
# chem
# httplib2
# openedx-calc
pyproject-api==1.7.1
# via tox
@@ -1181,7 +1264,7 @@ pysrt==1.1.2
# via
# -r requirements/edx/base.txt
# edxval
pytest==8.2.2
pytest==8.3.2
# via
# -r requirements/edx/testing.in
# pylint-pytest
@@ -1223,16 +1306,12 @@ python-dateutil==2.9.0.post0
# olxcleaner
# ora2
# xblock
python-dotenv==1.0.1
# via uvicorn
python-ipware==3.0.0
# via
# -r requirements/edx/base.txt
# django-ipware
python-memcached==1.62
# via -r requirements/edx/base.txt
python-multipart==0.0.9
# via fastapi
python-slugify==8.0.4
# via
# -r requirements/edx/base.txt
@@ -1268,7 +1347,7 @@ pytz==2024.1
# xblock
pyuca==1.2
# via -r requirements/edx/base.txt
pyyaml==6.0.1
pyyaml==6.0.2
# via
# -r requirements/edx/base.txt
# code-annotations
@@ -1277,13 +1356,12 @@ pyyaml==6.0.1
# edx-django-release-util
# edx-i18n-tools
# jsondiff
# uvicorn
# xblock
random2==1.0.2
# via -r requirements/edx/base.txt
recommender-xblock==2.2.0
# via -r requirements/edx/base.txt
redis==5.0.7
redis==5.0.8
# via
# -r requirements/edx/base.txt
# walrus
@@ -1292,7 +1370,7 @@ referencing==0.35.1
# -r requirements/edx/base.txt
# jsonschema
# jsonschema-specifications
regex==2024.5.15
regex==2024.7.24
# via
# -r requirements/edx/base.txt
# nltk
@@ -1301,12 +1379,15 @@ requests==2.32.3
# -r requirements/edx/base.txt
# algoliasearch
# analytics-python
# cachecontrol
# django-oauth-toolkit
# edx-bulk-grades
# edx-drf-extensions
# edx-enterprise
# edx-rest-api-client
# geoip2
# google-api-core
# google-cloud-storage
# mailsnake
# meilisearch
# openai
@@ -1325,15 +1406,15 @@ requests-oauthlib==2.0.0
# via
# -r requirements/edx/base.txt
# social-auth-core
rfc3986[idna2008]==1.5.0
# via httpx
rich==13.7.1
# via typer
rpds-py==0.19.0
rpds-py==0.20.0
# via
# -r requirements/edx/base.txt
# jsonschema
# referencing
rsa==4.9
# via
# -r requirements/edx/base.txt
# google-auth
rules==3.4
# via
# -r requirements/edx/base.txt
@@ -1357,11 +1438,9 @@ semantic-version==2.10.0
# via
# -r requirements/edx/base.txt
# edx-drf-extensions
shapely==2.0.5
shapely==2.0.6
# via -r requirements/edx/base.txt
shellingham==1.5.4
# via typer
simplejson==3.19.2
simplejson==3.19.3
# via
# -r requirements/edx/base.txt
# sailthru-client
@@ -1406,16 +1485,14 @@ slumber==0.7.1
# edx-enterprise
# edx-rest-api-client
sniffio==1.3.1
# via
# anyio
# httpcore
# httpx
snowflake-connector-python==3.11.0
# via anyio
snowflake-connector-python==3.12.0
# via
# -r requirements/edx/base.txt
# edx-enterprise
social-auth-app-django==5.4.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
# edx-auth-backends
social-auth-core==4.5.4
@@ -1431,7 +1508,7 @@ sortedcontainers==2.4.0
# via
# -r requirements/edx/base.txt
# snowflake-connector-python
soupsieve==2.5
soupsieve==2.6
# via
# -r requirements/edx/base.txt
# beautifulsoup4
@@ -1441,7 +1518,7 @@ sqlparse==0.5.1
# django
staff-graded-xblock==2.3.0
# via -r requirements/edx/base.txt
starlette==0.37.2
starlette==0.38.2
# via fastapi
stevedore==5.2.0
# via
@@ -1455,7 +1532,7 @@ super-csv==3.2.0
# via
# -r requirements/edx/base.txt
# edx-bulk-grades
sympy==1.13.0
sympy==1.13.2
# via
# -r requirements/edx/base.txt
# openedx-calc
@@ -1472,20 +1549,18 @@ tinycss2==1.2.1
# via
# -r requirements/edx/base.txt
# bleach
tomlkit==0.13.0
tomlkit==0.13.2
# via
# -r requirements/edx/base.txt
# pylint
# snowflake-connector-python
tox==4.16.0
tox==4.18.0
# via -r requirements/edx/testing.in
tqdm==4.66.4
tqdm==4.66.5
# via
# -r requirements/edx/base.txt
# nltk
# openai
typer==0.12.3
# via fastapi-cli
typing-extensions==4.12.2
# via
# -r requirements/edx/base.txt
@@ -1499,7 +1574,6 @@ typing-extensions==4.12.2
# pydantic-core
# pylti1p3
# snowflake-connector-python
# typer
tzdata==2024.1
# via
# -r requirements/edx/base.txt
@@ -1515,23 +1589,19 @@ uritemplate==4.1.1
# -r requirements/edx/base.txt
# drf-spectacular
# drf-yasg
# google-api-python-client
urllib3==1.26.19
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
# botocore
# elasticsearch
# pact-python
# py2neo
# requests
user-util==1.1.0
# via -r requirements/edx/base.txt
uvicorn[standard]==0.30.1
# via
# fastapi
# pact-python
uvloop==0.19.0
# via uvicorn
uvicorn==0.30.6
# via pact-python
vine==5.1.0
# via
# -r requirements/edx/base.txt
@@ -1548,10 +1618,8 @@ walrus==0.9.4
# via
# -r requirements/edx/base.txt
# edx-event-bus-redis
watchdog==4.0.1
watchdog==4.0.2
# via -r requirements/edx/base.txt
watchfiles==0.22.0
# via uvicorn
wcwidth==0.2.13
# via
# -r requirements/edx/base.txt
@@ -1570,18 +1638,17 @@ webencodings==0.5.1
# bleach
# html5lib
# tinycss2
webob==1.8.7
webob==1.8.8
# via
# -r requirements/edx/base.txt
# xblock
websockets==12.0
# via uvicorn
wrapt==1.16.0
# via
# -r requirements/edx/base.txt
# astroid
xblock[django]==4.0.1
# via
# -c requirements/edx/../constraints.txt
# -r requirements/edx/base.txt
# acid-xblock
# crowdsourcehinter-xblock
@@ -1617,7 +1684,8 @@ yarl==1.9.4
# via
# -r requirements/edx/base.txt
# aiohttp
zipp==3.19.2
# pact-python
zipp==3.20.0
# via
# -r requirements/edx/base.txt
# importlib-metadata

View File

@@ -18,7 +18,7 @@ pyproject-hooks==1.1.0
# via
# build
# pip-tools
wheel==0.43.0
wheel==0.44.0
# via pip-tools
# The following packages are considered to be unsafe in a requirements file:

View File

@@ -4,11 +4,11 @@
#
# make upgrade
#
wheel==0.43.0
wheel==0.44.0
# via -r requirements/pip.in
# The following packages are considered to be unsafe in a requirements file:
pip==24.1.2
pip==24.2
# via -r requirements/pip.in
setuptools==70.3.0
setuptools==73.0.0
# via -r requirements/pip.in

View File

@@ -32,7 +32,7 @@ pymongo==4.4.0
# via
# -r scripts/structures_pruning/requirements/base.txt
# edx-opaque-keys
pytest==8.2.2
pytest==8.3.2
# via -r scripts/structures_pruning/requirements/testing.in
stevedore==5.2.0
# via

View File

@@ -6,21 +6,21 @@
#
asgiref==3.8.1
# via django
attrs==23.2.0
attrs==24.2.0
# via zeep
backoff==2.2.1
# via -r scripts/user_retirement/requirements/base.in
boto3==1.34.144
boto3==1.35.1
# via -r scripts/user_retirement/requirements/base.in
botocore==1.34.144
botocore==1.35.1
# via
# boto3
# s3transfer
cachetools==5.4.0
cachetools==5.5.0
# via google-auth
certifi==2024.7.4
# via requests
cffi==1.16.0
cffi==1.17.0
# via
# cryptography
# pynacl
@@ -33,9 +33,9 @@ click==8.1.6
# -c scripts/user_retirement/requirements/../../../requirements/constraints.txt
# -r scripts/user_retirement/requirements/base.in
# edx-django-utils
cryptography==42.0.8
cryptography==43.0.0
# via pyjwt
django==4.2.14
django==4.2.15
# via
# -c scripts/user_retirement/requirements/../../../requirements/common_constraints.txt
# -c scripts/user_retirement/requirements/../../../requirements/constraints.txt
@@ -46,15 +46,15 @@ django-crum==0.7.9
# via edx-django-utils
django-waffle==4.1.0
# via edx-django-utils
edx-django-utils==5.14.2
edx-django-utils==5.15.0
# via edx-rest-api-client
edx-rest-api-client==5.7.1
# via -r scripts/user_retirement/requirements/base.in
google-api-core==2.19.1
# via google-api-python-client
google-api-python-client==2.137.0
google-api-python-client==2.141.0
# via -r scripts/user_retirement/requirements/base.in
google-auth==2.32.0
google-auth==2.34.0
# via
# google-api-core
# google-api-python-client
@@ -81,9 +81,9 @@ lxml==4.9.4
# via
# -c scripts/user_retirement/requirements/../../../requirements/constraints.txt
# zeep
more-itertools==10.3.0
more-itertools==10.4.0
# via simple-salesforce
newrelic==9.12.0
newrelic==9.13.0
# via edx-django-utils
pbr==6.0.0
# via stevedore
@@ -91,7 +91,7 @@ platformdirs==4.2.2
# via zeep
proto-plus==1.24.0
# via google-api-core
protobuf==5.27.2
protobuf==5.27.3
# via
# google-api-core
# googleapis-common-protos
@@ -106,7 +106,7 @@ pyasn1-modules==0.4.0
# via google-auth
pycparser==2.22
# via cffi
pyjwt[crypto]==2.8.0
pyjwt[crypto]==2.9.0
# via
# edx-rest-api-client
# simple-salesforce
@@ -120,7 +120,7 @@ pytz==2024.1
# via
# jenkinsapi
# zeep
pyyaml==6.0.1
pyyaml==6.0.2
# via -r scripts/user_retirement/requirements/base.in
requests==2.32.3
# via
@@ -143,7 +143,7 @@ s3transfer==0.10.2
# via boto3
simple-salesforce==1.12.6
# via -r scripts/user_retirement/requirements/base.in
simplejson==3.19.2
simplejson==3.19.3
# via -r scripts/user_retirement/requirements/base.in
six==1.16.0
# via

View File

@@ -8,23 +8,23 @@ asgiref==3.8.1
# via
# -r scripts/user_retirement/requirements/base.txt
# django
attrs==23.2.0
attrs==24.2.0
# via
# -r scripts/user_retirement/requirements/base.txt
# zeep
backoff==2.2.1
# via -r scripts/user_retirement/requirements/base.txt
boto3==1.34.144
boto3==1.35.1
# via
# -r scripts/user_retirement/requirements/base.txt
# moto
botocore==1.34.144
botocore==1.35.1
# via
# -r scripts/user_retirement/requirements/base.txt
# boto3
# moto
# s3transfer
cachetools==5.4.0
cachetools==5.5.0
# via
# -r scripts/user_retirement/requirements/base.txt
# google-auth
@@ -32,7 +32,7 @@ certifi==2024.7.4
# via
# -r scripts/user_retirement/requirements/base.txt
# requests
cffi==1.16.0
cffi==1.17.0
# via
# -r scripts/user_retirement/requirements/base.txt
# cryptography
@@ -45,14 +45,14 @@ click==8.1.6
# via
# -r scripts/user_retirement/requirements/base.txt
# edx-django-utils
cryptography==42.0.8
cryptography==43.0.0
# via
# -r scripts/user_retirement/requirements/base.txt
# moto
# pyjwt
ddt==1.7.2
# via -r scripts/user_retirement/requirements/testing.in
django==4.2.14
django==4.2.15
# via
# -r scripts/user_retirement/requirements/base.txt
# django-crum
@@ -66,7 +66,7 @@ django-waffle==4.1.0
# via
# -r scripts/user_retirement/requirements/base.txt
# edx-django-utils
edx-django-utils==5.14.2
edx-django-utils==5.15.0
# via
# -r scripts/user_retirement/requirements/base.txt
# edx-rest-api-client
@@ -76,9 +76,9 @@ google-api-core==2.19.1
# via
# -r scripts/user_retirement/requirements/base.txt
# google-api-python-client
google-api-python-client==2.137.0
google-api-python-client==2.141.0
# via -r scripts/user_retirement/requirements/base.txt
google-auth==2.32.0
google-auth==2.34.0
# via
# -r scripts/user_retirement/requirements/base.txt
# google-api-core
@@ -126,13 +126,13 @@ markupsafe==2.1.5
# werkzeug
mock==5.1.0
# via -r scripts/user_retirement/requirements/testing.in
more-itertools==10.3.0
more-itertools==10.4.0
# via
# -r scripts/user_retirement/requirements/base.txt
# simple-salesforce
moto==4.2.14
# via -r scripts/user_retirement/requirements/testing.in
newrelic==9.12.0
newrelic==9.13.0
# via
# -r scripts/user_retirement/requirements/base.txt
# edx-django-utils
@@ -152,7 +152,7 @@ proto-plus==1.24.0
# via
# -r scripts/user_retirement/requirements/base.txt
# google-api-core
protobuf==5.27.2
protobuf==5.27.3
# via
# -r scripts/user_retirement/requirements/base.txt
# google-api-core
@@ -175,7 +175,7 @@ pycparser==2.22
# via
# -r scripts/user_retirement/requirements/base.txt
# cffi
pyjwt[crypto]==2.8.0
pyjwt[crypto]==2.9.0
# via
# -r scripts/user_retirement/requirements/base.txt
# edx-rest-api-client
@@ -188,7 +188,7 @@ pyparsing==3.1.2
# via
# -r scripts/user_retirement/requirements/base.txt
# httplib2
pytest==8.2.2
pytest==8.3.2
# via -r scripts/user_retirement/requirements/testing.in
python-dateutil==2.9.0.post0
# via
@@ -200,7 +200,7 @@ pytz==2024.1
# -r scripts/user_retirement/requirements/base.txt
# jenkinsapi
# zeep
pyyaml==6.0.1
pyyaml==6.0.2
# via
# -r scripts/user_retirement/requirements/base.txt
# responses
@@ -242,7 +242,7 @@ s3transfer==0.10.2
# boto3
simple-salesforce==1.12.6
# via -r scripts/user_retirement/requirements/base.txt
simplejson==3.19.2
simplejson==3.19.3
# via -r scripts/user_retirement/requirements/base.txt
six==1.16.0
# via

View File

@@ -21,7 +21,7 @@ from scripts.user_retirement.tests.retirement_helpers import (
FAKE_USERNAMES,
TEST_RETIREMENT_QUEUE_STATES,
TEST_RETIREMENT_STATE,
get_fake_user_retirement
get_fake_user_retirement,
)
from scripts.user_retirement.utils import edx_api
@@ -499,46 +499,6 @@ class TestDiscoveryApi(OAuth2Mixin, unittest.TestCase):
)
class TestDemographicsApi(OAuth2Mixin, unittest.TestCase):
"""
Test the edX Demographics API client.
"""
@responses.activate(registry=OrderedRegistry)
def setUp(self):
super().setUp()
self.mock_access_token_response()
self.lms_base_url = 'http://localhost:18000/'
self.demographics_base_url = 'http://localhost:18360/'
self.demographics_api = edx_api.DemographicsApi(
self.lms_base_url,
self.demographics_base_url,
'the_client_id',
'the_client_secret'
)
def tearDown(self):
super().tearDown()
responses.reset()
@patch.object(edx_api.DemographicsApi, 'retire_learner')
def test_retire_learner(self, mock_method):
json_data = {
'lms_user_id': get_fake_user_retirement()['user']['id']
}
responses.add(
POST,
urljoin(self.demographics_base_url, 'demographics/api/v1/retire_demographics/'),
match=[matchers.json_params_matcher(json_data)]
)
self.demographics_api.retire_learner(
learner=get_fake_user_retirement()
)
mock_method.assert_called_once_with(
learner=get_fake_user_retirement()
)
class TestLicenseManagerApi(OAuth2Mixin, unittest.TestCase):
"""
Test the edX License Manager API client.

View File

@@ -1,6 +1,7 @@
"""
edX API classes which call edX service REST API endpoints using the edx-rest-api-client module.
"""
import logging
from urllib.parse import urljoin
@@ -28,6 +29,7 @@ class BaseApiClient:
"""
API client base class used to submit API requests to a particular web service.
"""
append_slash = True
_access_token = None
@@ -45,15 +47,15 @@ class BaseApiClient:
Args:
path (str): API endpoint path.
"""
path = path.strip('/')
path = path.strip("/")
if self.append_slash:
path += '/'
path += "/"
return urljoin(f'{self.api_base_url}/', path)
return urljoin(f"{self.api_base_url}/", path)
def _request(self, method, url, log_404_as_error=True, **kwargs):
if 'headers' not in kwargs:
kwargs['headers'] = {'Content-type': 'application/json'}
if "headers" not in kwargs:
kwargs["headers"] = {"Content-type": "application/json"}
try:
response = requests.request(method, url, auth=SuppliedJwtAuth(self._access_token), **kwargs)
@@ -68,7 +70,7 @@ class BaseApiClient:
# Immediately raise the error so that a 404 isn't logged as an API error in this case.
raise HttpDoesNotExistException(str(exc))
LOG.error(f'API Error: {str(exc)} with status code: {status_code}')
LOG.error(f"API Error: {str(exc)} with status code: {status_code}")
if status_code == 504:
# Differentiate gateway errors so different backoff can be used.
@@ -92,31 +94,31 @@ class BaseApiClient:
Returns:
str: JWT access token
"""
oauth_access_token_url = urljoin(f'{oauth_base_url}/', OAUTH_ACCESS_TOKEN_URL)
oauth_access_token_url = urljoin(f"{oauth_base_url}/", OAUTH_ACCESS_TOKEN_URL)
data = {
'grant_type': 'client_credentials',
'client_id': client_id,
'client_secret': client_secret,
'token_type': 'jwt',
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret,
"token_type": "jwt",
}
try:
response = requests.post(
oauth_access_token_url,
data=data,
headers={
'User-Agent': 'scripts.user_retirement',
"User-Agent": "scripts.user_retirement",
},
timeout=(REQUEST_CONNECT_TIMEOUT, REQUEST_READ_TIMEOUT)
timeout=(REQUEST_CONNECT_TIMEOUT, REQUEST_READ_TIMEOUT),
)
response.raise_for_status()
return response.json()['access_token']
return response.json()["access_token"]
except KeyError as exc:
LOG.error(f'Failed to get token. {str(exc)} does not exist.')
LOG.error(f"Failed to get token. {str(exc)} does not exist.")
raise
except HTTPError as exc:
LOG.error(
f'API Error: {str(exc)} with status code: {exc.response.status_code} fetching access token: {client_id}'
f"API Error: {str(exc)} with status code: {exc.response.status_code} fetching access token: {client_id}"
)
raise
@@ -125,7 +127,7 @@ def _backoff_handler(details):
"""
Simple logging handler for when timeout backoff occurs.
"""
LOG.info('Trying again in {wait:0.1f} seconds after {tries} tries calling {target}'.format(**details))
LOG.info("Trying again in {wait:0.1f} seconds after {tries} tries calling {target}".format(**details))
def _wait_one_minute():
@@ -143,10 +145,7 @@ def _giveup_on_unexpected_exception(exc):
# Treat a ConnectionError as retryable.
isinstance(exc, ConnectionError)
# All 5xx status codes are retryable except for 504 Gateway Timeout.
or (
500 <= exc.response.status_code < 600
and exc.response.status_code != 504 # Gateway Timeout
)
or (500 <= exc.response.status_code < 600 and exc.response.status_code != 504) # Gateway Timeout
# Status code 104 is unreserved, but we must have added this because we observed retryable 104 responses.
or exc.response.status_code == 104
)
@@ -167,7 +166,7 @@ def _retry_lms_api():
# Wrap the actual _backoff_handler so that we can patch the real one in unit tests. Otherwise, the func
# will get decorated on import, embedding this handler as a python object reference, precluding our ability
# to patch it in tests.
on_backoff=lambda details: _backoff_handler(details) # pylint: disable=unnecessary-lambda
on_backoff=lambda details: _backoff_handler(details), # pylint: disable=unnecessary-lambda
)
func_with_timeout_backoff = backoff.on_exception(
_wait_one_minute,
@@ -176,7 +175,7 @@ def _retry_lms_api():
# Wrap the actual _backoff_handler so that we can patch the real one in unit tests. Otherwise, the func
# will get decorated on import, embedding this handler as a python object reference, precluding our ability
# to patch it in tests.
on_backoff=lambda details: _backoff_handler(details) # pylint: disable=unnecessary-lambda
on_backoff=lambda details: _backoff_handler(details), # pylint: disable=unnecessary-lambda
)
return func_with_backoff(func_with_timeout_backoff(func))
@@ -193,14 +192,11 @@ class LmsApi(BaseApiClient):
"""
Retrieves a list of learners awaiting retirement actions.
"""
params = {
'cool_off_days': cool_off_days,
'states': states_to_request
}
params = {"cool_off_days": cool_off_days, "states": states_to_request}
if limit:
params['limit'] = limit
api_url = self.get_api_url('api/user/v1/accounts/retirement_queue')
return self._request('GET', api_url, params=params)
params["limit"] = limit
api_url = self.get_api_url("api/user/v1/accounts/retirement_queue")
return self._request("GET", api_url, params=params)
@_retry_lms_api()
def get_learners_by_date_and_status(self, state_to_request, start_date, end_date):
@@ -214,20 +210,20 @@ class LmsApi(BaseApiClient):
:param end_date: Date or Datetime
"""
params = {
'start_date': start_date.strftime('%Y-%m-%d'),
'end_date': end_date.strftime('%Y-%m-%d'),
'state': state_to_request
"start_date": start_date.strftime("%Y-%m-%d"),
"end_date": end_date.strftime("%Y-%m-%d"),
"state": state_to_request,
}
api_url = self.get_api_url('api/user/v1/accounts/retirements_by_status_and_date')
return self._request('GET', api_url, params=params)
api_url = self.get_api_url("api/user/v1/accounts/retirements_by_status_and_date")
return self._request("GET", api_url, params=params)
@_retry_lms_api()
def get_learner_retirement_state(self, username):
"""
Retrieves the given learner's retirement state.
"""
api_url = self.get_api_url(f'api/user/v1/accounts/{username}/retirement_status')
return self._request('GET', api_url)
api_url = self.get_api_url(f"api/user/v1/accounts/{username}/retirement_status")
return self._request("GET", api_url)
@_retry_lms_api()
def update_learner_retirement_state(self, username, new_state_name, message, force=False):
@@ -235,26 +231,22 @@ class LmsApi(BaseApiClient):
Updates the given learner's retirement state to the retirement state name new_string
with the additional string information in message (for logging purposes).
"""
data = {
'username': username,
'new_state': new_state_name,
'response': message
}
data = {"username": username, "new_state": new_state_name, "response": message}
if force:
data['force'] = True
data["force"] = True
api_url = self.get_api_url('api/user/v1/accounts/update_retirement_status')
return self._request('PATCH', api_url, json=data)
api_url = self.get_api_url("api/user/v1/accounts/update_retirement_status")
return self._request("PATCH", api_url, json=data)
@_retry_lms_api()
def retirement_deactivate_logout(self, learner):
"""
Performs the user deactivation and forced logout step of learner retirement
"""
data = {'username': learner['original_username']}
api_url = self.get_api_url('api/user/v1/accounts/deactivate_logout')
return self._request('POST', api_url, json=data)
data = {"username": learner["original_username"]}
api_url = self.get_api_url("api/user/v1/accounts/deactivate_logout")
return self._request("POST", api_url, json=data)
@_retry_lms_api()
def retirement_retire_forum(self, learner):
@@ -262,10 +254,10 @@ class LmsApi(BaseApiClient):
Performs the forum retirement step of learner retirement
"""
# api/discussion/
data = {'username': learner['original_username']}
data = {"username": learner["original_username"]}
try:
api_url = self.get_api_url('api/discussion/v1/accounts/retire_forum')
return self._request('POST', api_url, json=data)
api_url = self.get_api_url("api/discussion/v1/accounts/retire_forum")
return self._request("POST", api_url, json=data)
except HttpDoesNotExistException:
LOG.info("No information about learner retirement")
return True
@@ -275,18 +267,18 @@ class LmsApi(BaseApiClient):
"""
Performs the email list retirement step of learner retirement
"""
data = {'username': learner['original_username']}
api_url = self.get_api_url('api/user/v1/accounts/retire_mailings')
return self._request('POST', api_url, json=data)
data = {"username": learner["original_username"]}
api_url = self.get_api_url("api/user/v1/accounts/retire_mailings")
return self._request("POST", api_url, json=data)
@_retry_lms_api()
def retirement_unenroll(self, learner):
"""
Unenrolls the user from all courses
"""
data = {'username': learner['original_username']}
api_url = self.get_api_url('api/enrollment/v1/unenroll')
return self._request('POST', api_url, json=data)
data = {"username": learner["original_username"]}
api_url = self.get_api_url("api/enrollment/v1/unenroll")
return self._request("POST", api_url, json=data)
# This endpoint additionally returns 500 when the EdxNotes backend service is unavailable.
@_retry_lms_api()
@@ -294,9 +286,9 @@ class LmsApi(BaseApiClient):
"""
Deletes all the user's notes (aka. annotations)
"""
data = {'username': learner['original_username']}
api_url = self.get_api_url('api/edxnotes/v1/retire_user')
return self._request('POST', api_url, json=data)
data = {"username": learner["original_username"]}
api_url = self.get_api_url("api/edxnotes/v1/retire_user")
return self._request("POST", api_url, json=data)
@_retry_lms_api()
def retirement_lms_retire_misc(self, learner):
@@ -304,27 +296,27 @@ class LmsApi(BaseApiClient):
Deletes, blanks, or one-way hashes personal information in LMS as
defined in EDUCATOR-2802 and sub-tasks.
"""
data = {'username': learner['original_username']}
api_url = self.get_api_url('api/user/v1/accounts/retire_misc')
return self._request('POST', api_url, json=data)
data = {"username": learner["original_username"]}
api_url = self.get_api_url("api/user/v1/accounts/retire_misc")
return self._request("POST", api_url, json=data)
@_retry_lms_api()
def retirement_lms_retire(self, learner):
"""
Deletes, blanks, or one-way hashes all remaining personal information in LMS
"""
data = {'username': learner['original_username']}
api_url = self.get_api_url('api/user/v1/accounts/retire')
return self._request('POST', api_url, json=data)
data = {"username": learner["original_username"]}
api_url = self.get_api_url("api/user/v1/accounts/retire")
return self._request("POST", api_url, json=data)
@_retry_lms_api()
def retirement_partner_queue(self, learner):
"""
Calls LMS to add the given user to the retirement reporting queue
"""
data = {'username': learner['original_username']}
api_url = self.get_api_url('api/user/v1/accounts/retirement_partner_report')
return self._request('PUT', api_url, json=data)
data = {"username": learner["original_username"]}
api_url = self.get_api_url("api/user/v1/accounts/retirement_partner_report")
return self._request("PUT", api_url, json=data)
@_retry_lms_api()
def retirement_partner_report(self):
@@ -332,16 +324,16 @@ class LmsApi(BaseApiClient):
Retrieves the list of users to create partner reports for and set their status to
processing
"""
api_url = self.get_api_url('api/user/v1/accounts/retirement_partner_report')
return self._request('POST', api_url)
api_url = self.get_api_url("api/user/v1/accounts/retirement_partner_report")
return self._request("POST", api_url)
@_retry_lms_api()
def retirement_partner_cleanup(self, usernames):
"""
Removes the given users from the partner reporting queue
"""
api_url = self.get_api_url('api/user/v1/accounts/retirement_partner_report_cleanup')
return self._request('POST', api_url, json=usernames)
api_url = self.get_api_url("api/user/v1/accounts/retirement_partner_report_cleanup")
return self._request("POST", api_url, json=usernames)
@_retry_lms_api()
def retirement_retire_proctoring_data(self, learner):
@@ -349,7 +341,7 @@ class LmsApi(BaseApiClient):
Deletes or hashes learner data from edx-proctoring
"""
api_url = self.get_api_url(f"api/edx_proctoring/v1/retire_user/{learner['user']['id']}")
return self._request('POST', api_url)
return self._request("POST", api_url)
@_retry_lms_api()
def retirement_retire_proctoring_backend_data(self, learner):
@@ -357,16 +349,16 @@ class LmsApi(BaseApiClient):
Removes the given learner from 3rd party proctoring backends
"""
api_url = self.get_api_url(f"api/edx_proctoring/v1/retire_backend_user/{learner['user']['id']}")
return self._request('POST', api_url)
return self._request("POST", api_url)
@_retry_lms_api()
def bulk_cleanup_retirements(self, usernames):
"""
Deletes the retirements for all given usernames
"""
data = {'usernames': usernames}
api_url = self.get_api_url('api/user/v1/accounts/retirement_cleanup')
return self._request('POST', api_url, json=data)
data = {"usernames": usernames}
api_url = self.get_api_url("api/user/v1/accounts/retirement_cleanup")
return self._request("POST", api_url, json=data)
def replace_lms_usernames(self, username_mappings):
"""
@@ -377,8 +369,8 @@ class LmsApi(BaseApiClient):
[{current_un_1: desired_un_1}, {current_un_2: desired_un_2}]
"""
data = {"username_mappings": username_mappings}
api_url = self.get_api_url('api/user/v1/accounts/replace_usernames')
return self._request('POST', api_url, json=data)
api_url = self.get_api_url("api/user/v1/accounts/replace_usernames")
return self._request("POST", api_url, json=data)
def replace_forums_usernames(self, username_mappings):
"""
@@ -389,8 +381,8 @@ class LmsApi(BaseApiClient):
[{current_un_1: new_un_1}, {current_un_2: new_un_2}]
"""
data = {"username_mappings": username_mappings}
api_url = self.get_api_url('api/discussion/v1/accounts/replace_usernames')
return self._request('POST', api_url, json=data)
api_url = self.get_api_url("api/discussion/v1/accounts/replace_usernames")
return self._request("POST", api_url, json=data)
class EcommerceApi(BaseApiClient):
@@ -403,9 +395,9 @@ class EcommerceApi(BaseApiClient):
"""
Performs the learner retirement step for Ecommerce
"""
data = {'username': learner['original_username']}
api_url = self.get_api_url('api/v2/user/retire')
return self._request('POST', api_url, json=data)
data = {"username": learner["original_username"]}
api_url = self.get_api_url("api/v2/user/retire")
return self._request("POST", api_url, json=data)
@_retry_lms_api()
def get_tracking_key(self, learner):
@@ -414,7 +406,7 @@ class EcommerceApi(BaseApiClient):
ecommerce doesn't have access to the LMS user id.
"""
api_url = self.get_api_url(f"api/v2/retirement/tracking_id/{learner['original_username']}")
return self._request('GET', api_url)['ecommerce_tracking_id']
return self._request("GET", api_url)["ecommerce_tracking_id"]
def replace_usernames(self, username_mappings):
"""
@@ -425,8 +417,8 @@ class EcommerceApi(BaseApiClient):
[{current_un_1: new_un_1}, {current_un_2: new_un_2}]
"""
data = {"username_mappings": username_mappings}
api_url = self.get_api_url('api/v2/user_management/replace_usernames')
return self._request('POST', api_url, json=data)
api_url = self.get_api_url("api/v2/user_management/replace_usernames")
return self._request("POST", api_url, json=data)
class CredentialsApi(BaseApiClient):
@@ -439,9 +431,9 @@ class CredentialsApi(BaseApiClient):
"""
Performs the learner retirement step for Credentials
"""
data = {'username': learner['original_username']}
api_url = self.get_api_url('user/retire')
return self._request('POST', api_url, json=data)
data = {"username": learner["original_username"]}
api_url = self.get_api_url("user/retire")
return self._request("POST", api_url, json=data)
def replace_usernames(self, username_mappings):
"""
@@ -452,8 +444,8 @@ class CredentialsApi(BaseApiClient):
[{current_un_1: new_un_1}, {current_un_2: new_un_2}]
"""
data = {"username_mappings": username_mappings}
api_url = self.get_api_url('api/v2/replace_usernames')
return self._request('POST', api_url, json=data)
api_url = self.get_api_url("api/v2/replace_usernames")
return self._request("POST", api_url, json=data)
class DiscoveryApi(BaseApiClient):
@@ -470,30 +462,8 @@ class DiscoveryApi(BaseApiClient):
[{current_un_1: new_un_1}, {current_un_2: new_un_2}]
"""
data = {"username_mappings": username_mappings}
api_url = self.get_api_url('api/v1/replace_usernames')
return self._request('POST', api_url, json=data)
class DemographicsApi(BaseApiClient):
"""
Demographics API client.
"""
@_retry_lms_api()
def retire_learner(self, learner):
"""
Performs the learner retirement step for Demographics. Passes the learner's LMS User Id instead of username.
"""
data = {'lms_user_id': learner['user']['id']}
# If the user we are retiring has no data in the Demographics DB the request will return a 404. We
# catch the HTTPError and return True in order to prevent this error getting raised and
# incorrectly causing the learner to enter an ERROR state during retirement.
try:
api_url = self.get_api_url('demographics/api/v1/retire_demographics')
return self._request('POST', api_url, log_404_as_error=False, json=data)
except HttpDoesNotExistException:
LOG.info("No demographics data found for user")
return True
api_url = self.get_api_url("api/v1/replace_usernames")
return self._request("POST", api_url, json=data)
class LicenseManagerApi(BaseApiClient):
@@ -508,15 +478,15 @@ class LicenseManagerApi(BaseApiClient):
username.
"""
data = {
'lms_user_id': learner['user']['id'],
'original_username': learner['original_username'],
"lms_user_id": learner["user"]["id"],
"original_username": learner["original_username"],
}
# If the user we are retiring has no data in the License Manager DB the request will return a 404. We
# catch the HTTPError and return True in order to prevent this error getting raised and
# incorrectly causing the learner to enter an ERROR state during retirement.
try:
api_url = self.get_api_url('api/v1/retire_user')
return self._request('POST', api_url, log_404_as_error=False, json=data)
api_url = self.get_api_url("api/v1/retire_user")
return self._request("POST", api_url, log_404_as_error=False, json=data)
except HttpDoesNotExistException:
LOG.info("No license manager data found for user")
return True

View File

@@ -1,17 +1,5 @@
module.exports = {
entry: {
AboutBlockDisplay: [
'./xmodule/js/src/xmodule.js',
'./xmodule/js/src/html/display.js',
'./xmodule/js/src/javascript_loader.js',
'./xmodule/js/src/collapsible.js',
'./xmodule/js/src/html/imageModal.js',
'./xmodule/js/common_static/js/vendor/draggabilly.js'
],
AboutBlockEditor: [
'./xmodule/js/src/xmodule.js',
'./xmodule/js/src/html/edit.js'
],
AnnotatableBlockDisplay: [
'./xmodule/js/src/xmodule.js',
'./xmodule/js/src/html/display.js',
@@ -33,18 +21,6 @@ module.exports = {
'./xmodule/js/src/xmodule.js',
'./xmodule/js/src/sequence/edit.js'
],
CourseInfoBlockDisplay: [
'./xmodule/js/src/xmodule.js',
'./xmodule/js/src/html/display.js',
'./xmodule/js/src/javascript_loader.js',
'./xmodule/js/src/collapsible.js',
'./xmodule/js/src/html/imageModal.js',
'./xmodule/js/common_static/js/vendor/draggabilly.js'
],
CourseInfoBlockEditor: [
'./xmodule/js/src/xmodule.js',
'./xmodule/js/src/html/edit.js'
],
CustomTagBlockDisplay: './xmodule/js/src/xmodule.js',
CustomTagBlockEditor: [
'./xmodule/js/src/xmodule.js',
@@ -104,18 +80,6 @@ module.exports = {
'./xmodule/js/src/xmodule.js',
'./xmodule/js/src/sequence/edit.js'
],
StaticTabBlockDisplay: [
'./xmodule/js/src/xmodule.js',
'./xmodule/js/src/html/display.js',
'./xmodule/js/src/javascript_loader.js',
'./xmodule/js/src/collapsible.js',
'./xmodule/js/src/html/imageModal.js',
'./xmodule/js/common_static/js/vendor/draggabilly.js'
],
StaticTabBlockEditor: [
'./xmodule/js/src/xmodule.js',
'./xmodule/js/src/html/edit.js'
],
VideoBlockDisplay: [
'./xmodule/js/src/xmodule.js',
'./xmodule/js/src/video/10_main.js'

View File

@@ -30,8 +30,11 @@ def connect_to_mongodb(
handles AutoReconnect errors by retrying read operations, since these exceptions
typically indicate a temporary step-down condition for MongoDB.
"""
# If the MongoDB server uses a separate authentication database that should be specified here
auth_source = kwargs.get('authsource', '') or None
# If the MongoDB server uses a separate authentication database that should be specified here.
# Convert the lowercased authsource parameter to the camel-cased authSource expected by MongoClient.
auth_source = db
if auth_source_key := {'authSource', 'authsource'}.intersection(set(kwargs.keys())):
auth_source = kwargs.pop(auth_source_key.pop()) or db
# sanitize a kwarg which may be present and is no longer expected
# AED 2020-03-02 TODO: Remove this when 'auth_source' will no longer exist in kwargs
@@ -63,7 +66,7 @@ def connect_to_mongodb(
}
if user is not None and password is not None and not db.startswith('test_'):
connection_params.update({'username': user, 'password': password, 'authSource': db})
connection_params.update({'username': user, 'password': password, 'authSource': auth_source})
mongo_conn = pymongo.MongoClient(**connection_params)