diff --git a/.annotation_safe_list.yml b/.annotation_safe_list.yml index a5e997bf2f4a..c57eeeb2503d 100644 --- a/.annotation_safe_list.yml +++ b/.annotation_safe_list.yml @@ -9,13 +9,13 @@ # Via Django auth.Group: - ".. no_pii:" : "No PII" + ".. no_pii:": "No PII" auth.Permission: - ".. no_pii:" : "No PII" + ".. no_pii:": "No PII" auth.User: ".. pii:": "Contains username, password, and email address, retired in AccountRetirementView" - ".. pii_types:" : username, email_address, password - ".. pii_retirement:" : local_api + ".. pii_types:": username, email_address, password + ".. pii_retirement:": local_api contenttypes.ContentType: ".. no_pii:": "No PII" admin.LogEntry: @@ -27,6 +27,66 @@ sessions.Session: sites.Site: ".. no_pii:": "No PII" +# Automatically generated edx-platform models that can't be annotated +calendar_sync.HistoricalUserCalendarSyncConfig: + ".. no_pii:": "No PII" +certificates.HistoricalCertificateAllowlist: + ".. no_pii:": "No PII" +certificates.HistoricalCertificateDateOverride: + ".. no_pii:": "No PII" +certificates.HistoricalCertificateInvalidation: + ".. no_pii:": "No PII" +certificates.HistoricalGeneratedCertificate: + ".. pii:": "PII can exist in the generated certificate linked to in this model. Certificate data is currently retained." + ".. pii_types:": "name, username" + ".. pii_retirement:": "retained" +course_apps.HistoricalCourseAppStatus: + ".. no_pii:": "No PII" +course_goals.HistoricalCourseGoal: + ".. no_pii:": "No PII" +course_live.HistoricalCourseLiveConfiguration: + ".. no_pii:": "No PII" +course_modes.HistoricalCourseMode: + ".. no_pii:": "No PII" +course_overviews.HistoricalCourseOverview: + ".. no_pii:": "No PII" +discussions.HistoricalDiscussionsConfiguration: + ".. no_pii:": "No PII" +entitlements.HistoricalCourseEntitlement: + ".. no_pii:": "No PII" +entitlements.HistoricalCourseEntitlementSupportDetail: + ".. no_pii:": "No PII" +experiments.HistoricalExperimentKeyValue: + ".. no_pii:": "No PII" +external_user_ids.HistoricalExternalId: + ".. no_pii:": "We store external_user_id here, but do not consider that PII under OEP-30." +external_user_ids.HistoricalExternalIdType: + ".. no_pii:": "No PII" +grades.HistoricalPersistentSubsectionGradeOverride: + ".. no_pii:": "No PII" +instructor_task.HistoricalInstructorTaskSchedule: + ".. no_pii:": "No PII" +program_enrollments.HistoricalProgramCourseEnrollment: + ".. no_pii:": "No PII" +program_enrollments.HistoricalProgramEnrollment: + ".. pii:": "PII is found in the external key for a program enrollment" + ".. pii_types:": "other" + ".. pii_retirement:": "local_api" +programs.HistoricalProgramDiscussionsConfiguration: + ".. no_pii:": "No PII" +programs.HistoricalProgramLiveConfiguration: + ".. no_pii:": "No PII" +schedules.HistoricalSchedule: + ".. no_pii:": "No PII" +split_modulestore_django.HistoricalSplitModulestoreCourseIndex: + ".. no_pii:": "No PII" +student.HistoricalCourseEnrollment: + ".. no_pii:": "No PII" +student.HistoricalManualEnrollmentAudit: + ".. pii:": "Contains enrolled_email, retired in LMSAccountRetirementView" + ".. pii_types:": "email_address" + ".. pii_retirement:": "local_api" + # Automatically generated models in edx-enterprise that can't be annotated there consent.HistoricalDataSharingConsent: ".. pii:": "The username field inherited from Consent contains PII." @@ -45,7 +105,7 @@ enterprise.HistoricalEnterpriseCustomerCatalog: enterprise.HistoricalEnterpriseCustomerEntitlement: ".. no_pii:": "No PII" -# Via ORA2 +# Via edx-ora2, these can be removed once the models are annotated for real assessment.Assessment: ".. no_pii:": "No PII" assessment.AssessmentFeedback: @@ -127,10 +187,24 @@ djcelery.TaskState: djcelery.WorkerState: ".. no_pii:": "No PII" +# Via django-celery-results +django_celery_results.ChordCounter: + ".. no_pii:": "No PII" +django_celery_results.GroupResult: + ".. no_pii:": "No PII" +django_celery_results.TaskResult: + ".. no_pii:": "No PII" + # Via edx-oauth2-provider https://github.com/edx/edx-oauth2-provider edx_oauth2_provider.TrustedClient: ".. no_pii:": "No PII" +# Via edx-name-affirmation, not part of the openedx org +edx_name_affirmation.HistoricalVerifiedName: + ".. pii:": "Contains name fields." + ".. pii_types:": "name" + ".. pii_retirement:": "local_api" + # Via VAL edxval.CourseVideo: ".. no_pii:": "No PII" @@ -149,6 +223,12 @@ edxval.VideoImage: edxval.VideoTranscript: ".. no_pii:": "No PII" +# Via PyLTI1p3 +lti1p3_tool_config.LtiTool: + ".. no_pii:": "No PII" +lti1p3_tool_config.LtiToolKey: + ".. no_pii:": "No PII" + # Via Milestones milestones.CourseContentMilestone: ".. no_pii:": "No PII" @@ -190,6 +270,10 @@ oauth2_provider.Grant: ".. pii:": "Contains 3rd party authentication secrets. Retired in DeactivateLogoutView." ".. pii_types:": password, other ".. pii_retirement:": local_api +oauth2_provider.IDToken: + ".. pii:": "Contains 3rd party authentication secrets, currently this is retained until the token times out, but should be retired explicitly with the other models from this package." + ".. pii_types:": password, other + ".. pii_retirement:": retained oauth2_provider.RefreshToken: ".. pii:": "Contains 3rd party authentication secrets. Retired in DeactivateLogoutView." ".. pii_types:": password, other @@ -250,6 +334,8 @@ submissions.StudentItem: ".. no_pii:": "No PII" submissions.Submission: ".. no_pii:": "No PII" +submissions.TeamSubmission: + ".. no_pii:": "No PII" # Via sorl-thumbnail https://github.com/jazzband/sorl-thumbnail thumbnail.KVStore: diff --git a/.github/workflows/check-consistent-dependencies.yml b/.github/workflows/check-consistent-dependencies.yml index 048cbe6b006b..82298af70a54 100644 --- a/.github/workflows/check-consistent-dependencies.yml +++ b/.github/workflows/check-consistent-dependencies.yml @@ -15,7 +15,7 @@ defaults: jobs: check-requirements: name: Compile requirements - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: # Only run remaining steps if there are changes to requirements/** diff --git a/.github/workflows/ci-static-analysis.yml b/.github/workflows/ci-static-analysis.yml index 458e00fc6b1f..d989ff9db288 100644 --- a/.github/workflows/ci-static-analysis.yml +++ b/.github/workflows/ci-static-analysis.yml @@ -10,7 +10,7 @@ jobs: matrix: python-version: - "3.11" - os: ["ubuntu-22.04"] + os: ["ubuntu-24.04"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/migrations-check.yml b/.github/workflows/migrations-check.yml index 624caddd5309..84e334d68872 100644 --- a/.github/workflows/migrations-check.yml +++ b/.github/workflows/migrations-check.yml @@ -13,7 +13,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-22.04] + os: [ubuntu-24.04] python-version: - "3.11" # 'pinned' is used to install the latest patch version of Django @@ -126,7 +126,7 @@ jobs: if: always() needs: - check_migrations - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Decide whether the needed jobs succeeded or failed # uses: re-actors/alls-green@v1.2.1 diff --git a/.github/workflows/pylint-checks.yml b/.github/workflows/pylint-checks.yml index 8860aced7f92..9a654e09e711 100644 --- a/.github/workflows/pylint-checks.yml +++ b/.github/workflows/pylint-checks.yml @@ -8,7 +8,7 @@ on: jobs: run-pylint: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -75,7 +75,7 @@ jobs: if: always() needs: - run-pylint - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Decide whether the needed jobs succeeded or failed # uses: re-actors/alls-green@v1.2.1 diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index 84610123493c..310f9f83bf3d 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -13,7 +13,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-22.04] + os: [ubuntu-24.04] python-version: - "3.11" node-version: [20] diff --git a/.github/workflows/static-assets-check.yml b/.github/workflows/static-assets-check.yml index 4fe66e2a7778..e08b2dce8127 100644 --- a/.github/workflows/static-assets-check.yml +++ b/.github/workflows/static-assets-check.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-22.04] + os: [ubuntu-24.04] python-version: - "3.11" node-version: [18, 20] diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 854677b93cff..e691e16e47f1 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -15,7 +15,7 @@ concurrency: jobs: run-tests: name: ${{ matrix.shard_name }}(py=${{ matrix.python-version }},dj=${{ matrix.django-version }},mongo=${{ matrix.mongo-version }}) - runs-on: ubuntu-22.04 + runs-on: ${{ matrix.os-version }} strategy: matrix: python-version: @@ -43,22 +43,27 @@ jobs: - "xmodule-with-cms" mongo-version: - "7.0" + os-version: + - ubuntu-24.04 - # We only need to test older versions of Mongo with modules that directly interface with Mongo (that is: xmodule.modulestore) - # This code is left here as an example for future refernce in case we need to reduce the number of shards we're - # testing but still have good confidence with older versions of mongo. We use Mongo 4.4 in the example. + # It's useful to run some subset of the tests on the older version of Ubuntu + # so that we don't spend too many resources on this but can find major issues quickly + # while we're in a situation where we support two versions. This section may be commented + # out when not in use to easily add/drop future support for any given major dependency. # - # exclude: - # - mongo-version: "4.4" - # include: - # - shard_name: "xmodule-with-cms" - # python-version: "3.11" - # django-version: "pinned" - # mongo-version: "4.4" - # - shard_name: "xmodule-with-lms" - # python-version: "3.11" - # django-version: "pinned" - # mongo-version: "4.4" + # We're testing the older version of Ubuntu and running the xmodule tests since those rely on many + # dependent complex libraries and will hopefully catch most issues quickly. + include: + - shard_name: "xmodule-with-cms" + python-version: "3.11" + django-version: "pinned" + mongo-version: "7.0" + os-version: "ubuntu-22.04" + - shard_name: "xmodule-with-lms" + python-version: "3.11" + django-version: "pinned" + mongo-version: "7.0" + os-version: "ubuntu-22.04" steps: - name: checkout repo @@ -90,19 +95,10 @@ jobs: activate = 1 EOF - - name: install mongo version - run: | - if [[ "${{ matrix.mongo-version }}" != "4.4" ]]; then - wget -qO - https://www.mongodb.org/static/pgp/server-${{ matrix.mongo-version }}.asc | sudo apt-key add - - echo "deb https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/${{ matrix.mongo-version }} multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-${{ matrix.mongo-version }}.list - sudo apt-get update && sudo apt-get install -y mongodb-org="${{ matrix.mongo-version }}.*" - fi - - - name: start mongod server for tests - run: | - sudo mkdir -p /data/db - sudo chmod -R a+rw /data/db - mongod & + - name: Start MongoDB + uses: supercharge/mongodb-github-action@1.11.0 + with: + mongodb-version: ${{ matrix.mongo-version }} - name: Setup Python uses: actions/setup-python@v5 @@ -164,7 +160,7 @@ jobs: overwrite: true collect-and-verify: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Setup Python @@ -229,7 +225,7 @@ jobs: # https://github.com/orgs/community/discussions/33579 success: name: Unit tests successful - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 if: always() needs: [run-tests] steps: @@ -240,7 +236,7 @@ jobs: jobs: ${{ toJSON(needs) }} compile-warnings-report: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: [run-tests] steps: - uses: actions/checkout@v4 @@ -268,7 +264,7 @@ jobs: overwrite: true merge-artifacts: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: [compile-warnings-report] steps: - name: Merge Pytest Warnings JSON Artifacts @@ -288,7 +284,7 @@ jobs: # Combine and upload coverage reports. coverage: if: (github.repository == 'edx/edx-platform-private') || (github.repository == 'openedx/edx-platform' && (startsWith(github.base_ref, 'open-release') == false)) - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 needs: [run-tests] strategy: matrix: diff --git a/.github/workflows/verify-dunder-init.yml b/.github/workflows/verify-dunder-init.yml index 9d920238ebd4..c398c506730b 100644 --- a/.github/workflows/verify-dunder-init.yml +++ b/.github/workflows/verify-dunder-init.yml @@ -1,4 +1,4 @@ -name: CI +name: Verify Dunder __init__.py Files on: pull_request: diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 548ea72b3e17..c0db64e5e188 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,7 +3,7 @@ version: 2 build: os: "ubuntu-22.04" tools: - python: "3.12" + python: "3.11" sphinx: configuration: docs/conf.py diff --git a/README.rst b/README.rst index 61f21337ee25..54b91363a85f 100644 --- a/README.rst +++ b/README.rst @@ -71,10 +71,10 @@ System Dependencies ------------------- OS: -* Ubuntu 20.04 - * Ubuntu 22.04 +* Ubuntu 24.04 + Interperters/Tools: * Python 3.11 @@ -107,6 +107,15 @@ Language Packages: Some Python packages have system dependencies. For example, installing these packages on Debian or Ubuntu will require first running ``sudo apt install python3-dev default-libmysqlclient-dev build-essential pkg-config`` to satisfy the requirements of the ``mysqlclient`` Python package. +Codejail Setup +-------------- + +As a part of the baremetal setup, you will need to configure your system to +work properly with codejail. See the `codejail installation steps`_ for more +details. + +.. _codejail installation steps: https://github.com/openedx/codejail?tab=readme-ov-file#installation + Build Steps ----------- diff --git a/cms/djangoapps/contentstore/courseware_index.py b/cms/djangoapps/contentstore/courseware_index.py index 48647bf47bc6..d3b6f811d5f6 100644 --- a/cms/djangoapps/contentstore/courseware_index.py +++ b/cms/djangoapps/contentstore/courseware_index.py @@ -256,7 +256,8 @@ def prepare_item_index(item, skip_index=False, groups_usage_info=None): # Now index the content for item in structure.get_children(): prepare_item_index(item, groups_usage_info=groups_usage_info) - searcher.index(items_index, request_timeout=timeout) + if items_index: + searcher.index(items_index, request_timeout=timeout) cls.remove_deleted_items(searcher, structure_key, indexed_items) except Exception as err: # pylint: disable=broad-except # broad exception so that index operation does not prevent the rest of the application from working diff --git a/cms/djangoapps/contentstore/helpers.py b/cms/djangoapps/contentstore/helpers.py index e67e337e55fa..8ff1f1aa39cc 100644 --- a/cms/djangoapps/contentstore/helpers.py +++ b/cms/djangoapps/contentstore/helpers.py @@ -24,7 +24,7 @@ from xmodule.xml_block import XmlMixin from cms.djangoapps.models.settings.course_grading import CourseGradingModel -from cms.lib.xblock.upstream_sync import UpstreamLink, BadUpstream, BadDownstream, fetch_customizable_fields +from cms.lib.xblock.upstream_sync import UpstreamLink, UpstreamLinkException, fetch_customizable_fields from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers import openedx.core.djangoapps.content_staging.api as content_staging_api import openedx.core.djangoapps.content_tagging.api as content_tagging_api @@ -323,6 +323,56 @@ def import_staged_content_from_user_clipboard(parent_key: UsageKey, request) -> return new_xblock, notices +def _fetch_and_set_upstream_link( + copied_from_block: str, + copied_from_version_num: int, + temp_xblock: XBlock, + user: User +): + """ + Fetch and set upstream link for the given xblock. This function handles following cases: + * the xblock is copied from a v2 library; the library block is set as upstream. + * the xblock is copied from a course; no upstream is set, only copied_from_block is set. + * the xblock is copied from a course where the source block was imported from a library; the original libary block + is set as upstream. + """ + # Try to link the pasted block (downstream) to the copied block (upstream). + temp_xblock.upstream = copied_from_block + try: + UpstreamLink.get_for_block(temp_xblock) + except UpstreamLinkException: + # Usually this will fail. For example, if the copied block is a modulestore course block, it can't be an + # upstream. That's fine! Instead, we store a reference to where this block was copied from, in the + # 'copied_from_block' field (from AuthoringMixin). + + # In case if the source block was imported from a library, we need to check its upstream + # and set the same upstream link for the new block. + source_descriptor = modulestore().get_item(UsageKey.from_string(copied_from_block)) + if source_descriptor.upstream: + _fetch_and_set_upstream_link( + source_descriptor.upstream, + source_descriptor.upstream_version, + temp_xblock, + user, + ) + else: + # else we store a reference to where this block was copied from, in the 'copied_from_block' + # field (from AuthoringMixin). + temp_xblock.upstream = None + temp_xblock.copied_from_block = copied_from_block + else: + # But if it doesn't fail, then populate the `upstream_version` field based on what was copied. Note that + # this could be the latest published version, or it could be an an even newer draft version. + temp_xblock.upstream_version = copied_from_version_num + # Also, fetch upstream values (`upstream_display_name`, etc.). + # Recall that the copied block could be a draft. So, rather than fetching from the published upstream (which + # could be older), fetch from the copied block itself. That way, if an author customizes a field, but then + # later wants to restore it, it will restore to the value that the field had when the block was pasted. Of + # course, if the author later syncs updates from a *future* published upstream version, then that will fetch + # new values from the published upstream content. + fetch_customizable_fields(upstream=temp_xblock, downstream=temp_xblock, user=user) + + def _import_xml_node_to_parent( node, parent_xblock: XBlock, @@ -404,28 +454,7 @@ def _import_xml_node_to_parent( raise NotImplementedError("We don't yet support pasting XBlocks with children") temp_xblock.parent = parent_key if copied_from_block: - # Try to link the pasted block (downstream) to the copied block (upstream). - temp_xblock.upstream = copied_from_block - try: - UpstreamLink.get_for_block(temp_xblock) - except (BadDownstream, BadUpstream): - # Usually this will fail. For example, if the copied block is a modulestore course block, it can't be an - # upstream. That's fine! Instead, we store a reference to where this block was copied from, in the - # 'copied_from_block' field (from AuthoringMixin). - temp_xblock.upstream = None - temp_xblock.copied_from_block = copied_from_block - else: - # But if it doesn't fail, then populate the `upstream_version` field based on what was copied. Note that - # this could be the latest published version, or it could be an an even newer draft version. - temp_xblock.upstream_version = copied_from_version_num - # Also, fetch upstream values (`upstream_display_name`, etc.). - # Recall that the copied block could be a draft. So, rather than fetching from the published upstream (which - # could be older), fetch from the copied block itself. That way, if an author customizes a field, but then - # later wants to restore it, it will restore to the value that the field had when the block was pasted. Of - # course, if the author later syncs updates from a *future* published upstream version, then that will fetch - # new values from the published upstream content. - fetch_customizable_fields(upstream=temp_xblock, downstream=temp_xblock, user=user) - + _fetch_and_set_upstream_link(copied_from_block, copied_from_version_num, temp_xblock, user) # Save the XBlock into modulestore. We need to save the block and its parent for this to work: new_xblock = store.update_item(temp_xblock, user.id, allow_not_found=True) parent_xblock.children.append(new_xblock.location) @@ -436,7 +465,7 @@ def _import_xml_node_to_parent( # Allow an XBlock to do anything fancy it may need to when pasted from the clipboard. # These blocks may handle their own children or parenting if needed. Let them return booleans to # let us know if we need to handle these or not. - children_handed = new_xblock.studio_post_paste(store, node) + children_handled = new_xblock.studio_post_paste(store, node) if not children_handled: for child_node in child_nodes: diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py index 4ff9e7ce8d43..346f10d933b1 100644 --- a/cms/djangoapps/contentstore/management/commands/import.py +++ b/cms/djangoapps/contentstore/management/commands/import.py @@ -8,7 +8,7 @@ from openedx.core.djangoapps.django_comment_common.utils import are_permissions_roles_seeded, seed_permissions_roles from xmodule.contentstore.django import contentstore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order +from xmodule.modulestore.django import SignalHandler, modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.xml_importer import import_course_from_xml # lint-amnesty, pylint: disable=wrong-import-order from xmodule.util.sandboxing import DEFAULT_PYTHON_LIB_FILENAME # lint-amnesty, pylint: disable=wrong-import-order @@ -73,6 +73,10 @@ def handle(self, *args, **options): for course in course_items: course_id = course.id + # Importing is an act of publishing so send the course published signal. + SignalHandler.course_published.send_robust(sender=self, course_key=course_id) + + # Seed forum permission roles if we need to. if not are_permissions_roles_seeded(course_id): self.stdout.write(f'Seeding forum roles for course {course_id}\n') seed_permissions_roles(course_id) diff --git a/cms/djangoapps/contentstore/management/commands/reindex_course.py b/cms/djangoapps/contentstore/management/commands/reindex_course.py index accbc077c4fc..275eff50626d 100644 --- a/cms/djangoapps/contentstore/management/commands/reindex_course.py +++ b/cms/djangoapps/contentstore/management/commands/reindex_course.py @@ -4,9 +4,10 @@ import logging from textwrap import dedent from time import time -from datetime import date +from datetime import date, datetime from django.core.management import BaseCommand, CommandError +from django.conf import settings from elasticsearch import exceptions from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey @@ -42,6 +43,10 @@ def add_arguments(self, parser): parser.add_argument('--active', action='store_true', help='Reindex active courses only') + parser.add_argument('--from_inclusion_date', + action='store_true', + help='Reindex courses with a start date greater than COURSEWARE_SEARCH_INCLUSION_DATE' + ) parser.add_argument('--setup', action='store_true', help='Reindex all courses on developers stack setup') @@ -70,15 +75,17 @@ def handle(self, *args, **options): # pylint: disable=too-many-statements course_ids = options['course_ids'] all_option = options['all'] active_option = options['active'] + inclusion_date_option = options['from_inclusion_date'] setup_option = options['setup'] readable_option = options['warning'] index_all_courses_option = all_option or setup_option - if ((not course_ids and not (index_all_courses_option or active_option)) or - (course_ids and (index_all_courses_option or active_option))): + course_option_flag_option = index_all_courses_option or active_option or inclusion_date_option + + if (not course_ids and not course_option_flag_option) or (course_ids and course_option_flag_option): raise CommandError(( "reindex_course requires one or more s" - " OR the --all, --active or --setup flags." + " OR the --all, --active, --setup, or --from_inclusion_date flags." )) store = modulestore() @@ -97,14 +104,16 @@ def handle(self, *args, **options): # pylint: disable=too-many-statements logging.exception('Search Engine error - %s', exc) return - index_exists = searcher._es.indices.exists(index=index_name) # pylint: disable=protected-access + # Legacy Elasticsearch engine + if hasattr(searcher, '_es'): # pylint: disable=protected-access + index_exists = searcher._es.indices.exists(index=index_name) # pylint: disable=protected-access - index_mapping = searcher._es.indices.get_mapping( # pylint: disable=protected-access - index=index_name, - ) if index_exists else {} + index_mapping = searcher._es.indices.get_mapping( # pylint: disable=protected-access + index=index_name, + ) if index_exists else {} - if index_exists and index_mapping: - return + if index_exists and index_mapping: + return # if reindexing is done during devstack setup step, don't prompt the user if setup_option or query_yes_no(self.CONFIRMATION_PROMPT, default="no"): @@ -127,6 +136,20 @@ def handle(self, *args, **options): # pylint: disable=too-many-statements course_keys = list(map(lambda course: course.id, active_courses)) logging.warning(f'Selected {len(course_keys)} active courses over a total of {len(all_courses)}.') + elif inclusion_date_option: + # in case of --from_inclusion_date, we get the list of course keys from all courses + # that are stored in modulestore and filter out courses with a start date less than + # the settings defined COURSEWARE_SEARCH_INCLUSION_DATE + all_courses = modulestore().get_courses() + + inclusion_date = datetime.strptime( + settings.FEATURES.get('COURSEWARE_SEARCH_INCLUSION_DATE', '2020-01-01'), + '%Y-%m-%d' + ) + + # We keep the courses that has a start date and the start date is greater than the inclusion date + active_courses = filter(lambda course: course.start and (course.start >= inclusion_date), all_courses) + course_keys = list(map(lambda course: course.id, active_courses)) else: # in case course keys are provided as arguments diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_reindex_courses.py b/cms/djangoapps/contentstore/management/commands/tests/test_reindex_courses.py index 13d33c48f92a..6d14b4a339f2 100644 --- a/cms/djangoapps/contentstore/management/commands/tests/test_reindex_courses.py +++ b/cms/djangoapps/contentstore/management/commands/tests/test_reindex_courses.py @@ -40,6 +40,9 @@ def setUp(self): self.third_course = CourseFactory.create( org="test", course="course3", display_name="run1", start=None, end=None ) + self.fourth_course = CourseFactory.create( + org="test", course="course4", display_name="run1", start=datetime.min.today() - timedelta(weeks=60) + ) REINDEX_PATH_LOCATION = ( 'cms.djangoapps.contentstore.management.commands.reindex_course.CoursewareSearchIndexer.do_course_reindex' @@ -111,7 +114,9 @@ def test_given_all_key_prompts_and_reindexes_all_courses(self): call_command('reindex_course', all=True) patched_yes_no.assert_called_once_with(ReindexCommand.CONFIRMATION_PROMPT, default='no') - expected_calls = self._build_calls(self.first_course, self.second_course, self.third_course) + expected_calls = self._build_calls( + self.first_course, self.second_course, self.third_course, self.fourth_course + ) self.assertCountEqual(patched_index.mock_calls, expected_calls) def test_given_all_key_prompts_and_reindexes_all_courses_cancelled(self): @@ -134,5 +139,21 @@ def test_given_active_key_prompt(self): mock.patch(self.MODULESTORE_PATCH_LOCATION, mock.Mock(return_value=self.store)): call_command('reindex_course', active=True) - expected_calls = self._build_calls(self.first_course) + expected_calls = self._build_calls(self.first_course, self.fourth_course) + self.assertCountEqual(patched_index.mock_calls, expected_calls) + + @mock.patch.dict( + 'django.conf.settings.FEATURES', + {'COURSEWARE_SEARCH_INCLUSION_DATE': (datetime.min.today() - timedelta(weeks=52)).strftime('%Y-%m-%d')} + ) + def test_given_from_inclusion_date_key_prompt(self): + """ + Test that reindexes all courses that have a start date after a defined inclusion date + when --from_inclusion_date key is given. + """ + with mock.patch(self.REINDEX_PATH_LOCATION) as patched_index, \ + mock.patch(self.MODULESTORE_PATCH_LOCATION, mock.Mock(return_value=self.store)): + call_command('reindex_course', from_inclusion_date=True) + + expected_calls = self._build_calls(self.first_course, self.second_course) self.assertCountEqual(patched_index.mock_calls, expected_calls) diff --git a/cms/djangoapps/contentstore/rest_api/v1/serializers/__init__.py b/cms/djangoapps/contentstore/rest_api/v1/serializers/__init__.py index 6fe829ce0e3a..616204ef59c7 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/serializers/__init__.py +++ b/cms/djangoapps/contentstore/rest_api/v1/serializers/__init__.py @@ -6,6 +6,7 @@ from .course_index import CourseIndexSerializer from .course_rerun import CourseRerunSerializer from .course_team import CourseTeamSerializer +from .course_waffle_flags import CourseWaffleFlagsSerializer from .grading import CourseGradingModelSerializer, CourseGradingSerializer from .group_configurations import CourseGroupConfigurationsSerializer from .home import StudioHomeSerializer, CourseHomeTabSerializer, LibraryTabSerializer diff --git a/cms/djangoapps/contentstore/rest_api/v1/serializers/course_waffle_flags.py b/cms/djangoapps/contentstore/rest_api/v1/serializers/course_waffle_flags.py new file mode 100644 index 000000000000..33dd99792882 --- /dev/null +++ b/cms/djangoapps/contentstore/rest_api/v1/serializers/course_waffle_flags.py @@ -0,0 +1,154 @@ +""" +API Serializers for course waffle flags +""" + +from rest_framework import serializers + +from cms.djangoapps.contentstore import toggles + + +class CourseWaffleFlagsSerializer(serializers.Serializer): + """ + Serializer for course waffle flags + """ + use_new_home_page = serializers.SerializerMethodField() + use_new_custom_pages = serializers.SerializerMethodField() + use_new_schedule_details_page = serializers.SerializerMethodField() + use_new_advanced_settings_page = serializers.SerializerMethodField() + use_new_grading_page = serializers.SerializerMethodField() + use_new_updates_page = serializers.SerializerMethodField() + use_new_import_page = serializers.SerializerMethodField() + use_new_export_page = serializers.SerializerMethodField() + use_new_files_uploads_page = serializers.SerializerMethodField() + use_new_video_uploads_page = serializers.SerializerMethodField() + use_new_course_outline_page = serializers.SerializerMethodField() + use_new_unit_page = serializers.SerializerMethodField() + use_new_course_team_page = serializers.SerializerMethodField() + use_new_certificates_page = serializers.SerializerMethodField() + use_new_textbooks_page = serializers.SerializerMethodField() + use_new_group_configurations_page = serializers.SerializerMethodField() + enable_course_optimizer = serializers.SerializerMethodField() + + def get_course_key(self): + """ + Retrieve the course_key from the context + """ + return self.context.get("course_key") + + def get_use_new_home_page(self, obj): + """ + Method to get the use_new_home_page switch + """ + return toggles.use_new_home_page() + + def get_use_new_custom_pages(self, obj): + """ + Method to get the use_new_custom_pages switch + """ + course_key = self.get_course_key() + return toggles.use_new_custom_pages(course_key) + + def get_use_new_schedule_details_page(self, obj): + """ + Method to get the use_new_schedule_details_page switch + """ + course_key = self.get_course_key() + return toggles.use_new_schedule_details_page(course_key) + + def get_use_new_advanced_settings_page(self, obj): + """ + Method to get the use_new_advanced_settings_page switch + """ + course_key = self.get_course_key() + return toggles.use_new_advanced_settings_page(course_key) + + def get_use_new_grading_page(self, obj): + """ + Method to get the use_new_grading_page switch + """ + course_key = self.get_course_key() + return toggles.use_new_grading_page(course_key) + + def get_use_new_updates_page(self, obj): + """ + Method to get the use_new_updates_page switch + """ + course_key = self.get_course_key() + return toggles.use_new_updates_page(course_key) + + def get_use_new_import_page(self, obj): + """ + Method to get the use_new_import_page switch + """ + course_key = self.get_course_key() + return toggles.use_new_import_page(course_key) + + def get_use_new_export_page(self, obj): + """ + Method to get the use_new_export_page switch + """ + course_key = self.get_course_key() + return toggles.use_new_export_page(course_key) + + def get_use_new_files_uploads_page(self, obj): + """ + Method to get the use_new_files_uploads_page switch + """ + course_key = self.get_course_key() + return toggles.use_new_files_uploads_page(course_key) + + def get_use_new_video_uploads_page(self, obj): + """ + Method to get the use_new_video_uploads_page switch + """ + course_key = self.get_course_key() + return toggles.use_new_video_uploads_page(course_key) + + def get_use_new_course_outline_page(self, obj): + """ + Method to get the use_new_course_outline_page switch + """ + course_key = self.get_course_key() + return toggles.use_new_course_outline_page(course_key) + + def get_use_new_unit_page(self, obj): + """ + Method to get the use_new_unit_page switch + """ + course_key = self.get_course_key() + return toggles.use_new_unit_page(course_key) + + def get_use_new_course_team_page(self, obj): + """ + Method to get the use_new_course_team_page switch + """ + course_key = self.get_course_key() + return toggles.use_new_course_team_page(course_key) + + def get_use_new_certificates_page(self, obj): + """ + Method to get the use_new_certificates_page switch + """ + course_key = self.get_course_key() + return toggles.use_new_certificates_page(course_key) + + def get_use_new_textbooks_page(self, obj): + """ + Method to get the use_new_textbooks_page switch + """ + course_key = self.get_course_key() + return toggles.use_new_textbooks_page(course_key) + + def get_use_new_group_configurations_page(self, obj): + """ + Method to get the use_new_group_configurations_page switch + """ + course_key = self.get_course_key() + return toggles.use_new_group_configurations_page(course_key) + + def get_enable_course_optimizer(self, obj): + """ + Method to get the enable_course_optimizer waffle flag + """ + course_key = self.get_course_key() + return toggles.enable_course_optimizer(course_key) diff --git a/cms/djangoapps/contentstore/rest_api/v1/urls.py b/cms/djangoapps/contentstore/rest_api/v1/urls.py index e2afe48c96a4..349218679709 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/urls.py +++ b/cms/djangoapps/contentstore/rest_api/v1/urls.py @@ -17,6 +17,7 @@ CourseRerunView, CourseSettingsView, CourseVideosView, + CourseWaffleFlagsView, HomePageView, HomePageCoursesView, HomePageLibrariesView, @@ -131,6 +132,11 @@ VerticalContainerView.as_view(), name="container_vertical" ), + re_path( + fr'^course_waffle_flags(?:/{COURSE_ID_PATTERN})?$', + CourseWaffleFlagsView.as_view(), + name="course_waffle_flags" + ), # Authoring API # Do not use under v1 yet (Nov. 23). The Authoring API is still experimental and the v0 versions should be used diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/__init__.py b/cms/djangoapps/contentstore/rest_api/v1/views/__init__.py index dfba1e63f72f..89d8d56eaa11 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/views/__init__.py +++ b/cms/djangoapps/contentstore/rest_api/v1/views/__init__.py @@ -5,6 +5,7 @@ from .course_details import CourseDetailsView from .course_index import CourseIndexView from .course_rerun import CourseRerunView +from .course_waffle_flags import CourseWaffleFlagsView from .course_team import CourseTeamView from .grading import CourseGradingView from .group_configurations import CourseGroupConfigurationsView diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/course_waffle_flags.py b/cms/djangoapps/contentstore/rest_api/v1/views/course_waffle_flags.py new file mode 100644 index 000000000000..ba96ff2b4a6c --- /dev/null +++ b/cms/djangoapps/contentstore/rest_api/v1/views/course_waffle_flags.py @@ -0,0 +1,73 @@ +""" API Views for course waffle flags """ + +from opaque_keys.edx.keys import CourseKey +from rest_framework.decorators import APIView +from rest_framework.response import Response + +from openedx.core.lib.api.view_utils import view_auth_classes + +from ..serializers import CourseWaffleFlagsSerializer + + +@view_auth_classes(is_authenticated=True) +class CourseWaffleFlagsView(APIView): + """ + API view to retrieve course waffle flag settings for a specific course. + + This view provides a GET endpoint that returns the status of various waffle + flags for a given course. It requires the user to be authenticated. + """ + + def get(self, request, course_id=None): + """ + Retrieve the waffle flag settings for the specified course. + + Args: + request (HttpRequest): The HTTP request object. + course_id (str, optional): The ID of the course for which to retrieve + the waffle flag settings. If not provided, + defaults to None. + + Returns: + Response: A JSON response containing the status of various waffle flags + for the specified course. + + **Example Request** + + GET /api/contentstore/v1/course_waffle_flags + GET /api/contentstore/v1/course_waffle_flags/course-v1:test+test+test + + **Response Values** + + A JSON response containing the status of various waffle flags + for the specified course. + + **Example Response** + + ```json + { + "use_new_home_page": true, + "use_new_custom_pages": true, + "use_new_schedule_details_page": true, + "use_new_advanced_settings_page": true, + "use_new_grading_page": true, + "use_new_updates_page": true, + "use_new_import_page": true, + "use_new_export_page": true, + "use_new_files_uploads_page": true, + "use_new_video_uploads_page": false, + "use_new_course_outline_page": true, + "use_new_unit_page": false, + "use_new_course_team_page": true, + "use_new_certificates_page": true, + "use_new_textbooks_page": true, + "use_new_group_configurations_page": true + } + ``` + """ + course_key = CourseKey.from_string(course_id) if course_id else None + serializer = CourseWaffleFlagsSerializer( + context={"course_key": course_key}, data={} + ) + serializer.is_valid(raise_exception=True) + return Response(serializer.data) diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_waffle_flags.py b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_waffle_flags.py new file mode 100644 index 000000000000..f0332d7f2870 --- /dev/null +++ b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_waffle_flags.py @@ -0,0 +1,129 @@ +""" +Unit tests for the course waffle flags view +""" + +from django.contrib.auth import get_user_model +from django.urls import reverse +from rest_framework import status + +from cms.djangoapps.contentstore.tests.utils import CourseTestCase +from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel + +User = get_user_model() + + +class CourseWaffleFlagsViewTest(CourseTestCase): + """ + Tests for the CourseWaffleFlagsView endpoint, which returns waffle flag states + for a specific course or globally if no course ID is provided. + """ + + course_waffle_flags = [ + "use_new_custom_pages", + "use_new_schedule_details_page", + "use_new_advanced_settings_page", + "use_new_grading_page", + "use_new_updates_page", + "use_new_import_page", + "use_new_export_page", + "use_new_files_uploads_page", + "use_new_video_uploads_page", + "use_new_course_outline_page", + "use_new_unit_page", + "use_new_course_team_page", + "use_new_certificates_page", + "use_new_textbooks_page", + "use_new_group_configurations_page", + ] + + other_expected_waffle_flags = ["enable_course_optimizer"] + + def setUp(self): + """ + Set up test data and state before each test method. + + This method initializes the endpoint URL and creates a set of waffle flags + for the test course, setting each flag's value to `True`. + """ + super().setUp() + self.url = reverse("cms.djangoapps.contentstore:v1:course_waffle_flags") + self.create_waffle_flags(self.course_waffle_flags) + self.create_custom_waffle_flags() + + def create_custom_waffle_flags(self, enabled=True): + """ + Helper method to create waffle flags that are not part of `course_waffle_flags` and have + a different format. + """ + WaffleFlagCourseOverrideModel.objects.create( + waffle_flag="contentstore.enable_course_optimizer", + course_id=self.course.id, + enabled=enabled, + ) + + def create_waffle_flags(self, flags, enabled=True): + """ + Helper method to create waffle flag entries in the database for the test course. + + Args: + flags (list): A list of flag names to set up. + enabled (bool): The value to set for each flag's enabled state. + """ + for flag in flags: + WaffleFlagCourseOverrideModel.objects.create( + waffle_flag=f"contentstore.new_studio_mfe.{flag}", + course_id=self.course.id, + enabled=enabled, + ) + + def expected_response(self, enabled=False): + """ + Generate an expected response dictionary based on the enabled flag. + + Args: + enabled (bool): State to assign to each waffle flag in the response. + + Returns: + dict: A dictionary with each flag set to the value of `enabled`. + """ + res = {flag: enabled for flag in self.course_waffle_flags} + for flag in self.other_expected_waffle_flags: + res[flag] = enabled + return res + + def test_get_course_waffle_flags_with_course_id(self): + """ + Test that waffle flags for a specific course are correctly returned when + a valid course ID is provided. + + Expected Behavior: + - The response should return HTTP 200 status. + - Each flag returned should be `True` as set up in the `setUp` method. + """ + course_url = reverse( + "cms.djangoapps.contentstore:v1:course_waffle_flags", + kwargs={"course_id": self.course.id}, + ) + + expected_response = self.expected_response(enabled=True) + expected_response["use_new_home_page"] = False + + response = self.client.get(course_url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(expected_response, response.data) + + def test_get_course_waffle_flags_without_course_id(self): + """ + Test that the default waffle flag states are returned when no course ID is provided. + + Expected Behavior: + - The response should return HTTP 200 status. + - Each flag returned should default to `False`, representing the global + default state for each flag. + """ + expected_response = self.expected_response(enabled=False) + expected_response["use_new_home_page"] = False + + response = self.client.get(self.url) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertDictEqual(expected_response, response.data) diff --git a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py index 69eee524373c..73e3fe5ec3ad 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py +++ b/cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py @@ -117,7 +117,7 @@ def test_home_page_response(self): "courses": [{ "course_key": course_id, "display_name": self.course.display_name, - "lms_link": f'//{settings.LMS_BASE}/courses/{course_id}/jump_to/{self.course.location}', + "lms_link": f'{settings.LMS_ROOT_URL}/courses/{course_id}/jump_to/{self.course.location}', "number": self.course.number, "org": self.course.org, "rerun_link": f'/course_rerun/{course_id}', @@ -144,7 +144,7 @@ def test_home_page_response_with_api_v2(self): OrderedDict([ ("course_key", course_id), ("display_name", self.course.display_name), - ("lms_link", f'//{settings.LMS_BASE}/courses/{course_id}/jump_to/{self.course.location}'), + ("lms_link", f'{settings.LMS_ROOT_URL}/courses/{course_id}/jump_to/{self.course.location}'), ("number", self.course.number), ("org", self.course.org), ("rerun_link", f'/course_rerun/{course_id}'), diff --git a/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_home.py b/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_home.py index c0ffa50903cd..6905de254f3e 100644 --- a/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_home.py +++ b/cms/djangoapps/contentstore/rest_api/v2/views/tests/test_home.py @@ -62,7 +62,7 @@ def test_home_page_response(self): OrderedDict([ ("course_key", course_id), ("display_name", self.course.display_name), - ("lms_link", f'//{settings.LMS_BASE}/courses/{course_id}/jump_to/{self.course.location}'), + ("lms_link", f'{settings.LMS_ROOT_URL}/courses/{course_id}/jump_to/{self.course.location}'), ("cms_link", f'//{settings.CMS_BASE}{reverse_course_url("course_handler", self.course.id)}'), ("number", self.course.number), ("org", self.course.org), @@ -76,7 +76,7 @@ def test_home_page_response(self): ("display_name", self.archived_course.display_name), ( "lms_link", - f'//{settings.LMS_BASE}/courses/{archived_course_id}/jump_to/{self.archived_course.location}' + f'{settings.LMS_ROOT_URL}/courses/{archived_course_id}/jump_to/{self.archived_course.location}' ), ( "cms_link", @@ -139,7 +139,7 @@ def test_active_only_query_if_passed(self): self.assertEqual(response.data["results"]["courses"], [OrderedDict([ ("course_key", str(self.course.id)), ("display_name", self.course.display_name), - ("lms_link", f'//{settings.LMS_BASE}/courses/{str(self.course.id)}/jump_to/{self.course.location}'), + ("lms_link", f'{settings.LMS_ROOT_URL}/courses/{str(self.course.id)}/jump_to/{self.course.location}'), ("cms_link", f'//{settings.CMS_BASE}{reverse_course_url("course_handler", self.course.id)}'), ("number", self.course.number), ("org", self.course.org), @@ -164,7 +164,11 @@ def test_archived_only_query_if_passed(self): ("display_name", self.archived_course.display_name), ( "lms_link", - f'//{settings.LMS_BASE}/courses/{str(self.archived_course.id)}/jump_to/{self.archived_course.location}', + '{url_root}/courses/{course_id}/jump_to/{location}'.format( + url_root=settings.LMS_ROOT_URL, + course_id=str(self.archived_course.id), + location=self.archived_course.location + ), ), ("cms_link", f'//{settings.CMS_BASE}{reverse_course_url("course_handler", self.archived_course.id)}'), ("number", self.archived_course.number), @@ -190,7 +194,11 @@ def test_search_query_if_passed(self): ("display_name", self.archived_course.display_name), ( "lms_link", - f'//{settings.LMS_BASE}/courses/{str(self.archived_course.id)}/jump_to/{self.archived_course.location}', + '{url_root}/courses/{course_id}/jump_to/{location}'.format( + url_root=settings.LMS_ROOT_URL, + course_id=str(self.archived_course.id), + location=self.archived_course.location + ), ), ("cms_link", f'//{settings.CMS_BASE}{reverse_course_url("course_handler", self.archived_course.id)}'), ("number", self.archived_course.number), diff --git a/cms/djangoapps/contentstore/tests/test_courseware_index.py b/cms/djangoapps/contentstore/tests/test_courseware_index.py index 98a60dce901f..3ab3fa373f81 100644 --- a/cms/djangoapps/contentstore/tests/test_courseware_index.py +++ b/cms/djangoapps/contentstore/tests/test_courseware_index.py @@ -504,6 +504,11 @@ def test_delete_course_from_search_index_after_course_deletion(self): """ Test for removing course from CourseAboutSearchIndexer """ self._test_delete_course_from_search_index_after_course_deletion(self.store) + def test_empty_course(self): + empty_course = CourseFactory.create(modulestore=self.store, start=datetime(2015, 3, 1, tzinfo=UTC)) + added_to_index = CoursewareSearchIndexer.do_course_reindex(self.store, empty_course.id) + assert added_to_index == 0 + @patch('django.conf.settings.SEARCH_ENGINE', 'search.tests.utils.ForceRefreshElasticSearchEngine') @ddt.ddt diff --git a/cms/djangoapps/contentstore/toggles.py b/cms/djangoapps/contentstore/toggles.py index 79c722e24d52..39b793f479a2 100644 --- a/cms/djangoapps/contentstore/toggles.py +++ b/cms/djangoapps/contentstore/toggles.py @@ -667,3 +667,23 @@ def libraries_v2_enabled(): search_api.is_meilisearch_enabled() and not DISABLE_NEW_LIBRARIES.is_enabled() ) + + +# .. toggle_name: contentstore.enable_course_optimizer +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: This flag enables the use of unique anonymous_user_id during studio preview +# .. toggle_use_cases: temporary +# .. toggle_creation_date: 2022-05-04 +# .. toggle_target_removal_date: 2022-05-30 +# .. toggle_tickets: MST-1455 +ENABLE_COURSE_OPTIMIZER = CourseWaffleFlag( + f'{CONTENTSTORE_NAMESPACE}.enable_course_optimizer', __name__ +) + + +def enable_course_optimizer(course_id): + """ + Returns a boolean if individualized anonymous_user_id is enabled on the course + """ + return ENABLE_COURSE_OPTIMIZER.is_enabled(course_id) diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index df1d1e27b405..c0f656ec7059 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -9,7 +9,7 @@ from collections import defaultdict from contextlib import contextmanager from datetime import datetime, timezone -from urllib.parse import quote_plus +from urllib.parse import quote_plus, urlencode, urlunparse, urlparse from uuid import uuid4 from bs4 import BeautifulSoup @@ -193,31 +193,30 @@ def get_lms_link_for_item(location, preview=False): """ assert isinstance(location, UsageKey) - # checks LMS_BASE value in site configuration for the given course_org_filter(org) - # if not found returns settings.LMS_BASE + # checks LMS_ROOT_URL value in site configuration for the given course_org_filter(org) + # if not found returns settings.LMS_ROOT_URL lms_base = SiteConfiguration.get_value_for_org( location.org, - "LMS_BASE", - settings.LMS_BASE + "LMS_ROOT_URL", + settings.LMS_ROOT_URL ) + query_string = '' if lms_base is None: return None if preview: - # checks PREVIEW_LMS_BASE value in site configuration for the given course_org_filter(org) - # if not found returns settings.FEATURES.get('PREVIEW_LMS_BASE') - lms_base = SiteConfiguration.get_value_for_org( - location.org, - "PREVIEW_LMS_BASE", - settings.FEATURES.get('PREVIEW_LMS_BASE') - ) + params = {'preview': '1'} + query_string = urlencode(params) - return "//{lms_base}/courses/{course_key}/jump_to/{location}".format( - lms_base=lms_base, + url_parts = list(urlparse(lms_base)) + url_parts[2] = '/courses/{course_key}/jump_to/{location}'.format( course_key=str(location.course_key), location=str(location), ) + url_parts[4] = query_string + + return urlunparse(url_parts) def get_lms_link_for_certificate_web_view(course_key, mode): diff --git a/cms/djangoapps/contentstore/video_storage_handlers.py b/cms/djangoapps/contentstore/video_storage_handlers.py index 6827b295b0a8..4cc5c738b5dc 100644 --- a/cms/djangoapps/contentstore/video_storage_handlers.py +++ b/cms/djangoapps/contentstore/video_storage_handlers.py @@ -967,26 +967,38 @@ def get_course_youtube_edx_video_ids(course_id): """ Get a list of youtube edx_video_ids """ - error_msg = "Invalid course_key: '%s'." % course_id - try: + invalid_key_error_msg = "Invalid course_key: '%s'." % course_id + unexpected_error_msg = "Unexpected error occurred for course_id: '%s'." % course_id + + try: # lint-amnesty, pylint: disable=too-many-nested-blocks course_key = CourseKey.from_string(course_id) course = modulestore().get_course(course_key) - except InvalidKeyError: - return JsonResponse({'error': error_msg}, status=500) - blocks = [] - block_yt_field = 'youtube_id_1_0' - block_edx_id_field = 'edx_video_id' - if hasattr(course, 'get_children'): - for section in course.get_children(): - for subsection in section.get_children(): - for vertical in subsection.get_children(): - for block in vertical.get_children(): - blocks.append(block) - - edx_video_ids = [] - for block in blocks: - if hasattr(block, block_yt_field) and getattr(block, block_yt_field): - if getattr(block, block_edx_id_field): - edx_video_ids.append(getattr(block, block_edx_id_field)) + + blocks = [] + block_yt_field = 'youtube_id_1_0' + block_edx_id_field = 'edx_video_id' + if hasattr(course, 'get_children'): + for section in course.get_children(): + for subsection in section.get_children(): + for vertical in subsection.get_children(): + for block in vertical.get_children(): + blocks.append(block) + + edx_video_ids = [] + for block in blocks: + if hasattr(block, block_yt_field) and getattr(block, block_yt_field): + if getattr(block, block_edx_id_field): + edx_video_ids.append(getattr(block, block_edx_id_field)) + + except InvalidKeyError as error: + LOGGER.exception( + f"InvalidKeyError occurred while getting YouTube video IDs for course_id: {course_id}: {error}" + ) + return JsonResponse({'error': invalid_key_error_msg}, status=500) + except Exception as error: + LOGGER.exception( + f"Unexpected error occurred while getting YouTube video IDs for course_id: {course_id}: {error}" + ) + return JsonResponse({'error': unexpected_error_msg}, status=500) return JsonResponse({'edx_video_ids': edx_video_ids}, status=200) diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py index 46f2dd322efa..914c07884665 100644 --- a/cms/djangoapps/contentstore/views/component.py +++ b/cms/djangoapps/contentstore/views/component.py @@ -26,7 +26,12 @@ from common.djangoapps.xblock_django.api import authorable_xblocks, disabled_xblocks from common.djangoapps.xblock_django.models import XBlockStudioConfigurationFlag from cms.djangoapps.contentstore.helpers import is_unit -from cms.djangoapps.contentstore.toggles import libraries_v2_enabled, use_new_problem_editor, use_new_unit_page +from cms.djangoapps.contentstore.toggles import ( + libraries_v1_enabled, + libraries_v2_enabled, + use_new_problem_editor, + use_new_unit_page, +) from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import load_services_for_studio from openedx.core.lib.xblock_utils import get_aside_from_xblock, is_xblock_aside from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration @@ -44,17 +49,19 @@ # NOTE: This list is disjoint from ADVANCED_COMPONENT_TYPES COMPONENT_TYPES = [ - 'discussion', - 'library', - 'library_v2', # Not an XBlock - 'itembank', 'html', - 'openassessment', - 'problem', 'video', + 'problem', + 'itembank', + 'library_v2', # Not an XBlock + 'library', + 'discussion', + 'openassessment', 'drag-and-drop-v2', ] +BETA_COMPONENT_TYPES = ['library_v2', 'itembank'] + ADVANCED_COMPONENT_TYPES = sorted({name for name, class_ in XBlock.load_classes()} - set(COMPONENT_TYPES)) ADVANCED_PROBLEM_TYPES = settings.ADVANCED_PROBLEM_TYPES @@ -274,7 +281,14 @@ def create_support_legend_dict(): component_types = COMPONENT_TYPES[:] # Libraries do not support discussions, drag-and-drop, and openassessment and other libraries - component_not_supported_by_library = ['discussion', 'library', 'openassessment', 'drag-and-drop-v2'] + component_not_supported_by_library = [ + 'discussion', + 'library', + 'openassessment', + 'drag-and-drop-v2', + 'library_v2', + 'itembank', + ] if library: component_types = [component for component in component_types if component not in set(component_not_supported_by_library)] @@ -414,7 +428,8 @@ def create_support_legend_dict(): "type": category, "templates": templates_for_category, "display_name": component_display_names[category], - "support_legend": create_support_legend_dict() + "support_legend": create_support_legend_dict(), + "beta": category in BETA_COMPONENT_TYPES, }) # Libraries do not support advanced components at this time. @@ -464,7 +479,7 @@ def create_support_legend_dict(): course_advanced_keys ) if advanced_component_templates['templates']: - component_templates.insert(0, advanced_component_templates) + component_templates.append(advanced_component_templates) return component_templates @@ -488,6 +503,8 @@ def _filter_disabled_blocks(all_blocks): Filter out disabled xblocks from the provided list of xblock names. """ disabled_block_names = [block.name for block in disabled_xblocks()] + if not libraries_v1_enabled(): + disabled_block_names.append('library') if not libraries_v2_enabled(): disabled_block_names.append('library_v2') disabled_block_names.append('itembank') diff --git a/cms/djangoapps/contentstore/views/tests/test_block.py b/cms/djangoapps/contentstore/views/tests/test_block.py index f3e20b45b2ea..a6fefe5f554c 100644 --- a/cms/djangoapps/contentstore/views/tests/test_block.py +++ b/cms/djangoapps/contentstore/views/tests/test_block.py @@ -3184,13 +3184,13 @@ def _verify_advanced_xblocks(self, expected_xblocks, expected_support_levels): templates = get_component_templates(self.course) button_names = [template["display_name"] for template in templates] self.assertIn("Advanced", button_names) - self.assertEqual(len(templates[0]["templates"]), len(expected_xblocks)) + self.assertEqual(len(templates[-1]["templates"]), len(expected_xblocks)) template_display_names = [ - template["display_name"] for template in templates[0]["templates"] + template["display_name"] for template in templates[-1]["templates"] ] self.assertEqual(template_display_names, expected_xblocks) template_support_levels = [ - template["support_level"] for template in templates[0]["templates"] + template["support_level"] for template in templates[-1]["templates"] ] self.assertEqual(template_support_levels, expected_support_levels) diff --git a/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py b/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py index 68771ceaec04..9244ffa989b6 100644 --- a/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py +++ b/cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py @@ -455,26 +455,6 @@ def setUp(self): self.lib_block_tags = ['tag_1', 'tag_5'] tagging_api.tag_object(str(self.lib_block_key), taxonomy_all_org, self.lib_block_tags) - def test_paste_from_library_creates_link(self): - """ - When we copy a v2 lib block into a course, the dest block should be linked up to the lib block. - """ - copy_response = self.client.post(CLIPBOARD_ENDPOINT, {"usage_key": str(self.lib_block_key)}, format="json") - assert copy_response.status_code == 200 - - paste_response = self.client.post(XBLOCK_ENDPOINT, { - "parent_locator": str(self.course.usage_key), - "staged_content": "clipboard", - }, format="json") - assert paste_response.status_code == 200 - - new_block_key = UsageKey.from_string(paste_response.json()["locator"]) - new_block = modulestore().get_item(new_block_key) - assert new_block.upstream == str(self.lib_block_key) - assert new_block.upstream_version == 3 - assert new_block.upstream_display_name == "MCQ-draft" - assert new_block.upstream_max_attempts == 5 - def test_paste_from_library_read_only_tags(self): """ When we copy a v2 lib block into a course, the dest block should have read-only copied tags. @@ -555,6 +535,34 @@ def test_paste_from_library_copies_asset(self): assert image_asset.name == "1px.webp" assert image_asset.length == len(webp_raw_data) + def test_paste_from_course_block_imported_from_library_creates_link(self): + """ + When we copy a course xblock which was imported or copied from v2 lib block into a course, + the dest block should be linked up to the original lib block. + """ + def _copy_paste_and_assert_link(key_to_copy): + copy_response = self.client.post(CLIPBOARD_ENDPOINT, {"usage_key": str(key_to_copy)}, format="json") + assert copy_response.status_code == 200 + + paste_response = self.client.post(XBLOCK_ENDPOINT, { + "parent_locator": str(self.course.usage_key), + "staged_content": "clipboard", + }, format="json") + assert paste_response.status_code == 200 + + new_block_key = UsageKey.from_string(paste_response.json()["locator"]) + new_block = modulestore().get_item(new_block_key) + assert new_block.upstream == str(self.lib_block_key) + assert new_block.upstream_version == 3 + assert new_block.upstream_display_name == "MCQ-draft" + assert new_block.upstream_max_attempts == 5 + return new_block_key + + # first verify link for copied block from library + new_block_key = _copy_paste_and_assert_link(self.lib_block_key) + # next verify link for copied block from the pasted block + _copy_paste_and_assert_link(new_block_key) + class ClipboardPasteFromV1LibraryTestCase(ModuleStoreTestCase): """ diff --git a/cms/djangoapps/contentstore/views/tests/test_library.py b/cms/djangoapps/contentstore/views/tests/test_library.py index 8278cd0535bb..b6c5765c5108 100644 --- a/cms/djangoapps/contentstore/views/tests/test_library.py +++ b/cms/djangoapps/contentstore/views/tests/test_library.py @@ -403,6 +403,8 @@ def test_get_component_templates(self): self.assertNotIn('advanced', templates) self.assertNotIn('openassessment', templates) self.assertNotIn('library', templates) + self.assertNotIn('library_v2', templates) + self.assertNotIn('itembank', templates) def test_advanced_problem_types(self): """ diff --git a/cms/envs/common.py b/cms/envs/common.py index ea374bca8bb3..a72af4c9f37f 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -566,6 +566,14 @@ # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2024-04-10 'BADGES_ENABLED': False, + + # .. toggle_name: FEATURES['IN_CONTEXT_DISCUSSION_ENABLED_DEFAULT'] + # .. toggle_implementation: DjangoSetting + # .. toggle_default: True + # .. toggle_description: Set to False to disable in-context discussion for units by default. + # .. toggle_use_cases: open_edx + # .. toggle_creation_date: 2024-09-02 + 'IN_CONTEXT_DISCUSSION_ENABLED_DEFAULT': True, } # .. toggle_name: ENABLE_COPPA_COMPLIANCE @@ -2726,7 +2734,7 @@ PASSWORD_RESET_EMAIL_RATE = '2/h' ######################## Setting for content libraries ######################## -MAX_BLOCKS_PER_CONTENT_LIBRARY = 1000 +MAX_BLOCKS_PER_CONTENT_LIBRARY = 100_000 ################# Student Verification ################# VERIFY_STUDENT = { diff --git a/cms/static/js/models/component_template.js b/cms/static/js/models/component_template.js index b2306d40e9c8..358b7b2f3433 100644 --- a/cms/static/js/models/component_template.js +++ b/cms/static/js/models/component_template.js @@ -11,7 +11,8 @@ define(['backbone'], function(Backbone) { // boilerplate_name (may be null) // is_common (only used for problems) templates: [], - support_legend: {} + support_legend: {}, + beta: false, }, parse: function(response) { // Returns true only for templates that both have no boilerplate and are of @@ -26,6 +27,7 @@ define(['backbone'], function(Backbone) { this.templates = response.templates; this.display_name = response.display_name; this.support_legend = response.support_legend; + this.beta = response.beta; // Sort the templates. this.templates.sort(function(a, b) { diff --git a/cms/static/js/views/components/add_xblock_button.js b/cms/static/js/views/components/add_xblock_button.js index 36b420a61068..4fa4d2effeff 100644 --- a/cms/static/js/views/components/add_xblock_button.js +++ b/cms/static/js/views/components/add_xblock_button.js @@ -8,7 +8,8 @@ define(['js/views/baseview', 'edx-ui-toolkit/js/utils/html-utils'], var attributes = { type: this.model.type, templates: this.model.templates, - display_name: this.model.display_name + display_name: this.model.display_name, + beta: this.model.beta, }; BaseView.prototype.initialize.call(this); this.template = this.loadTemplate('add-xblock-component-button'); diff --git a/cms/static/js/views/modals/preview_v2_library_changes.js b/cms/static/js/views/modals/preview_v2_library_changes.js index 282132898895..943fe103220e 100644 --- a/cms/static/js/views/modals/preview_v2_library_changes.js +++ b/cms/static/js/views/modals/preview_v2_library_changes.js @@ -4,9 +4,8 @@ * authors to preview the new version of a library-sourced XBlock, and decide * whether to accept ("sync") or reject ("ignore") the changes. */ -define(['jquery', 'underscore', 'gettext', 'js/views/modals/base_modal', - 'common/js/components/utils/view_utils', 'js/views/utils/xblock_utils'], -function($, _, gettext, BaseModal, ViewUtils, XBlockViewUtils) { +define(['jquery', 'underscore', 'gettext', 'js/views/modals/base_modal', 'common/js/components/utils/view_utils'], +function($, _, gettext, BaseModal, ViewUtils) { 'use strict'; var PreviewLibraryChangesModal = BaseModal.extend({ @@ -40,18 +39,24 @@ function($, _, gettext, BaseModal, ViewUtils, XBlockViewUtils) { /** * Show an edit modal for the specified xblock - * @param xblockElement The element that contains the xblock to be edited. - * @param rootXBlockInfo An XBlockInfo model that describes the root xblock on the page. + * @param xblockInfo The XBlockInfo model that describes the xblock. + * @param courseAuthoringMfeUrl The course authoring mfe url. + * @param upstreamBlockId The library block id. + * @param upstreamBlockVersionSynced The library block current version. * @param refreshFunction A function to refresh the block after it has been updated */ - showPreviewFor: function(xblockElement, rootXBlockInfo, refreshFunction) { - this.xblockElement = xblockElement; - this.xblockInfo = XBlockViewUtils.findXBlockInfo(xblockElement, rootXBlockInfo); - this.courseAuthoringMfeUrl = rootXBlockInfo.attributes.course_authoring_url; - const headerElement = xblockElement.find('.xblock-header-primary'); + showPreviewFor: function( + xblockInfo, + courseAuthoringMfeUrl, + upstreamBlockId, + upstreamBlockVersionSynced, + refreshFunction + ) { + this.xblockInfo = xblockInfo; + this.courseAuthoringMfeUrl = courseAuthoringMfeUrl; this.downstreamBlockId = this.xblockInfo.get('id'); - this.upstreamBlockId = headerElement.data('upstream-ref'); - this.upstreamBlockVersionSynced = headerElement.data('version-synced'); + this.upstreamBlockId = upstreamBlockId; + this.upstreamBlockVersionSynced = upstreamBlockVersionSynced; this.refreshFunction = refreshFunction; this.render(); diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js index be7088746ff8..1d57eb92b807 100644 --- a/cms/static/js/views/pages/container.js +++ b/cms/static/js/views/pages/container.js @@ -128,6 +128,14 @@ function($, _, Backbone, gettext, BasePage, } + if (this.options.isIframeEmbed) { + window.addEventListener('message', (event) => { + if (event.data && event.data.type === 'refreshXBlock') { + this.render(); + } + }); + } + this.listenTo(Backbone, 'move:onXBlockMoved', this.onXBlockMoved); }, @@ -434,15 +442,43 @@ function($, _, Backbone, gettext, BasePage, /** Show the modal for previewing changes before syncing a library-sourced XBlock. */ showXBlockLibraryChangesPreview: function(event, options) { - event.preventDefault(); + const xblockElement = this.findXBlockElement(event.target); + const self = this; + const xblockInfo = XBlockUtils.findXBlockInfo(xblockElement, this.model); + const courseAuthoringMfeUrl = this.model.attributes.course_authoring_url; + const headerElement = xblockElement.find('.xblock-header-primary'); + const upstreamBlockId = headerElement.data('upstream-ref'); + const upstreamBlockVersionSynced = headerElement.data('version-synced'); - var xblockElement = this.findXBlockElement(event.target), - self = this, - modal = new PreviewLibraryChangesModal(options); + try { + if (this.options.isIframeEmbed) { + window.parent.postMessage( + { + type: 'showXBlockLibraryChangesPreview', + payload: { + downstreamBlockId: xblockInfo.get('id'), + displayName: xblockInfo.get('display_name'), + isVertical: xblockInfo.isVertical(), + upstreamBlockId, + upstreamBlockVersionSynced, + } + }, document.referrer + ); + return true; + } + } catch (e) { + console.error(e); + } - modal.showPreviewFor(xblockElement, this.model, function() { - self.refreshXBlock(xblockElement, false); - }); + event.preventDefault(); + var modal = new PreviewLibraryChangesModal(options); + modal.showPreviewFor( + xblockInfo, + courseAuthoringMfeUrl, + upstreamBlockId, + upstreamBlockVersionSynced, + function() { self.refreshXBlock(xblockElement, false); } + ); }, /** Show the multi-select library content picker, for adding to a Problem Bank (itembank) Component */ @@ -625,26 +661,40 @@ function($, _, Backbone, gettext, BasePage, }, showMoveXBlockModal: function(event) { + var xblockElement = this.findXBlockElement(event.target), + parentXBlockElement = xblockElement.parents('.studio-xblock-wrapper'), + sourceXBlockInfo = XBlockUtils.findXBlockInfo(xblockElement, this.model), + sourceParentXBlockInfo = XBlockUtils.findXBlockInfo(parentXBlockElement, this.model), + modal = new MoveXBlockModal({ + sourceXBlockInfo: sourceXBlockInfo, + sourceParentXBlockInfo: sourceParentXBlockInfo, + XBlockURLRoot: this.getURLRoot(), + outlineURL: this.options.outlineURL + }); + try { if (this.options.isIframeEmbed) { window.parent.postMessage( { type: 'showMoveXBlockModal', - payload: {} + payload: { + sourceXBlockInfo: { + id: sourceXBlockInfo.attributes.id, + displayName: sourceXBlockInfo.attributes.display_name, + }, + sourceParentXBlockInfo: { + id: sourceParentXBlockInfo.attributes.id, + category: sourceParentXBlockInfo.attributes.category, + hasChildren: sourceParentXBlockInfo.attributes.has_children, + }, + }, }, document.referrer ); + return true; } } catch (e) { console.error(e); } - var xblockElement = this.findXBlockElement(event.target), - parentXBlockElement = xblockElement.parents('.studio-xblock-wrapper'), - modal = new MoveXBlockModal({ - sourceXBlockInfo: XBlockUtils.findXBlockInfo(xblockElement, this.model), - sourceParentXBlockInfo: XBlockUtils.findXBlockInfo(parentXBlockElement, this.model), - XBlockURLRoot: this.getURLRoot(), - outlineURL: this.options.outlineURL - }); event.preventDefault(); modal.show(); diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js index b32c86ae8dec..cfb47c7cd895 100644 --- a/cms/static/js/views/pages/container_subviews.js +++ b/cms/static/js/views/pages/container_subviews.js @@ -506,9 +506,13 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H }, renderTagElements: function(tags, depth, parentId) { + /* This function displays the tags in the sidebar of the legacy Unit Outline Page. + * It is not used when the Authoring MFE iframes a component in the Unit Outline. */ + const parentElement = document.querySelector(`.content-tags-${parentId}`); + if (!parentElement) return; + const tagListElement = this; tags.forEach(function(tag) { - const parentElement = document.querySelector(`.content-tags-${parentId}`); var tagContentElement = document.createElement('div'), tagValueElement = document.createElement('span'); diff --git a/cms/static/sass/course-unit-mfe-iframe-bundle.scss b/cms/static/sass/course-unit-mfe-iframe-bundle.scss index 95774ea74058..a71882f1c355 100644 --- a/cms/static/sass/course-unit-mfe-iframe-bundle.scss +++ b/cms/static/sass/course-unit-mfe-iframe-bundle.scss @@ -43,7 +43,7 @@ .wrapper-xblock .header-actions .actions-list .action-item .action-button { @extend %button-styles; - color: $black; + color: $primary; .fa-ellipsis-v { font-size: $base-font-size; @@ -51,6 +51,7 @@ &:hover { background-color: $primary; + color: $white; border-color: $transparent; } @@ -373,6 +374,12 @@ } } +.library-sync-button { + .action-button-text { + display: none; + } +} + .action-edit { .action-button-text { display: none; diff --git a/cms/templates/js/add-xblock-component-button.underscore b/cms/templates/js/add-xblock-component-button.underscore index b591c336938c..df100d6dfcd1 100644 --- a/cms/templates/js/add-xblock-component-button.underscore +++ b/cms/templates/js/add-xblock-component-button.underscore @@ -6,7 +6,7 @@ <%- gettext("Add Component:") %> <%- display_name %> - <% if (type === 'library_v2' || type === 'itembank') { %> + <% if (beta) { %> <%- gettext("Beta") %> <% } %> diff --git a/cms/templates/studio_xblock_wrapper.html b/cms/templates/studio_xblock_wrapper.html index 14685963c904..41555410236a 100644 --- a/cms/templates/studio_xblock_wrapper.html +++ b/cms/templates/studio_xblock_wrapper.html @@ -143,7 +143,7 @@ data-tooltip="${_("Update available - click to sync")}" > - ${_("Update available")} + ${_("Update available")} % endif diff --git a/common/djangoapps/student/models/course_enrollment.py b/common/djangoapps/student/models/course_enrollment.py index 09862916e321..750ac66e38c0 100644 --- a/common/djangoapps/student/models/course_enrollment.py +++ b/common/djangoapps/student/models/course_enrollment.py @@ -1750,7 +1750,7 @@ def refund_window(self, refund_window): class BulkUnenrollConfiguration(ConfigurationModel): # lint-amnesty, pylint: disable=empty-docstring """ - + .. no_pii: """ csv_file = models.FileField( validators=[FileExtensionValidator(allowed_extensions=['csv'])], @@ -1763,6 +1763,8 @@ class BulkUnenrollConfiguration(ConfigurationModel): # lint-amnesty, pylint: di class BulkChangeEnrollmentConfiguration(ConfigurationModel): """ config model for the bulk_change_enrollment_csv command + + .. no_pii: """ csv_file = models.FileField( validators=[FileExtensionValidator(allowed_extensions=['csv'])], diff --git a/common/djangoapps/student/models/user.py b/common/djangoapps/student/models/user.py index aa3de546ef2b..9d979beb19cb 100644 --- a/common/djangoapps/student/models/user.py +++ b/common/djangoapps/student/models/user.py @@ -1685,6 +1685,8 @@ class AllowedAuthUser(TimeStampedModel): class AccountRecoveryConfiguration(ConfigurationModel): """ configuration model for recover account management command + + .. no_pii: """ csv_file = models.FileField( validators=[FileExtensionValidator(allowed_extensions=['csv'])], @@ -1824,6 +1826,8 @@ def perform_streak_updates(cls, user, course_key, browser_timezone=None): class UserPasswordToggleHistory(TimeStampedModel): """ Keeps track of user password disable/enable history + + .. no_pii: """ user = models.ForeignKey(User, related_name='password_toggle_history', on_delete=models.CASCADE) comment = models.CharField(max_length=255, help_text=_("Add a reason"), blank=True, null=True) diff --git a/common/djangoapps/student/tests/test_linkedin.py b/common/djangoapps/student/tests/test_linkedin.py index 50dd921f25a9..a5de21595bb6 100644 --- a/common/djangoapps/student/tests/test_linkedin.py +++ b/common/djangoapps/student/tests/test_linkedin.py @@ -38,28 +38,20 @@ class LinkedInAddToProfileUrlTests(TestCase): def test_linked_in_url(self, cert_mode, expected_cert_name): config = LinkedInAddToProfileConfigurationFactory() - # We can switch to this once edx-platform reaches Python 3.8 - # expected_url = ( - # 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&' - # 'name={platform}+{cert_name}&certUrl={cert_url}&' - # 'organizationId={company_identifier}' - # ).format( - # platform=quote(settings.PLATFORM_NAME.encode('utf-8')), - # cert_name=expected_cert_name, - # cert_url=quote(self.CERT_URL, safe=''), - # company_identifier=config.company_identifier, - # ) + expected_url = ( + 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&' + 'name={platform}+{cert_name}&certUrl={cert_url}&' + 'organizationId={company_identifier}' + ).format( + platform=quote(settings.PLATFORM_NAME.encode('utf-8')), + cert_name=expected_cert_name, + cert_url=quote(self.CERT_URL, safe=''), + company_identifier=config.company_identifier, + ) actual_url = config.add_to_profile_url(self.COURSE_NAME, cert_mode, self.CERT_URL) - # We can switch to this instead of the assertIn once edx-platform reaches Python 3.8 - # There was a problem with dict ordering in the add_to_profile_url function that will go away then. - # self.assertEqual(actual_url, expected_url) - - assert 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME' in actual_url - assert f'&name={quote(settings.PLATFORM_NAME.encode("utf-8"))}+{expected_cert_name}' in actual_url - assert '&certUrl={cert_url}'.format(cert_url=quote(self.CERT_URL, safe='')) in actual_url - assert f'&organizationId={config.company_identifier}' in actual_url + self.assertEqual(actual_url, expected_url) @ddt.data( ('honor', 'Honor+Code+Credential+for+Test+Course+%E2%98%83'), @@ -72,26 +64,18 @@ def test_linked_in_url(self, cert_mode, expected_cert_name): def test_linked_in_url_with_cert_name_override(self, cert_mode, expected_cert_name): config = LinkedInAddToProfileConfigurationFactory() - # We can switch to this once edx-platform reaches Python 3.8 - # expected_url = ( - # 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&' - # 'name={platform}+{cert_name}&certUrl={cert_url}&' - # 'organizationId={company_identifier}' - # ).format( - # platform=quote(settings.PLATFORM_NAME.encode('utf-8')), - # cert_name=expected_cert_name, - # cert_url=quote(self.CERT_URL, safe=''), - # company_identifier=config.company_identifier, - # ) + expected_url = ( + 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&' + 'name={platform}+{cert_name}&certUrl={cert_url}&' + 'organizationId={company_identifier}' + ).format( + platform=quote(settings.PLATFORM_NAME.encode('utf-8')), + cert_name=expected_cert_name, + cert_url=quote(self.CERT_URL, safe=''), + company_identifier=config.company_identifier, + ) with with_site_configuration_context(configuration=self.SITE_CONFIGURATION): actual_url = config.add_to_profile_url(self.COURSE_NAME, cert_mode, self.CERT_URL) - # We can switch to this instead of the assertIn once edx-platform reaches Python 3.8 - # There was a problem with dict ordering in the add_to_profile_url function that will go away then. - # self.assertEqual(actual_url, expected_url) - - assert 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME' in actual_url - assert f'&name={quote(settings.PLATFORM_NAME.encode("utf-8"))}+{expected_cert_name}' in actual_url - assert '&certUrl={cert_url}'.format(cert_url=quote(self.CERT_URL, safe='')) in actual_url - assert f'&organizationId={config.company_identifier}' in actual_url + self.assertEqual(actual_url, expected_url) diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index 9e61095260b6..c640462acf10 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -440,28 +440,19 @@ def test_linked_in_add_to_profile_btn_with_certificate(self): assert response.status_code == 200 self.assertContains(response, 'Add Certificate to LinkedIn') - # We can switch to this and the commented out assertContains once edx-platform reaches Python 3.8 - # expected_url = ( - # 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&' - # 'name={platform}+Honor+Code+Certificate+for+Omega&certUrl={cert_url}&' - # 'organizationId={company_identifier}' - # ).format( - # platform=quote(settings.PLATFORM_NAME.encode('utf-8')), - # cert_url=quote(cert.download_url, safe=''), - # company_identifier=linkedin_config.company_identifier, - # ) - - # self.assertContains(response, escape(expected_url)) - - # These can be removed (in favor of the above) once we are on Python 3.8. Fails in 3.5 because of dict ordering - self.assertContains(response, escape('https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME')) - self.assertContains(response, escape('&name={platform}+Honor+Code+Certificate+for+Omega'.format( - platform=quote(settings.PLATFORM_NAME.encode('utf-8')) - ))) - self.assertContains(response, escape('&certUrl={cert_url}'.format(cert_url=quote(cert.download_url, safe='')))) - self.assertContains(response, escape('&organizationId={company_identifier}'.format( + expected_url = ( + 'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&' + 'name={platform}+Honor+Code+Certificate+for+Omega&' + 'certUrl={cert_url}&' + 'organizationId={company_identifier}' + ).format( + platform=quote(settings.PLATFORM_NAME.encode('utf-8')), + cert_url=quote(cert.download_url, safe=''), company_identifier=linkedin_config.company_identifier - ))) + ) + + # Single assertion for the expected LinkedIn URL + self.assertContains(response, escape(expected_url)) @skip_unless_lms def test_dashboard_metadata_caching(self): diff --git a/common/djangoapps/util/cache.py b/common/djangoapps/util/cache.py index ca2ee2050eae..3ec99efc636d 100644 --- a/common/djangoapps/util/cache.py +++ b/common/djangoapps/util/cache.py @@ -53,13 +53,11 @@ def wrapper(request, *args, **kwargs): # specifically the branding index, to do authentication. # If that page is cached the authentication doesn't # happen, so we disable the cache when that feature is enabled. - if ( - not request.user.is_authenticated - ): + if not request.user.is_authenticated: # Use the cache. The same view accessed through different domain names may # return different things, so include the domain name in the key. - domain = str(request.META.get('HTTP_HOST')) + '.' - cache_key = domain + "cache_if_anonymous." + get_language() + '.' + request.path + domain = request.META.get('HTTP_HOST', '') + '.' + cache_key = f"{domain}cache_if_anonymous.{get_language()}.{request.path}" # Include the values of GET parameters in the cache key. for get_parameter in get_parameters: @@ -67,24 +65,20 @@ def wrapper(request, *args, **kwargs): if parameter_value is not None: # urlencode expects data to be of type str, and doesn't deal well with Unicode data # since it doesn't provide a way to specify an encoding. - cache_key = cache_key + '.' + urlencode({ - get_parameter: str(parameter_value).encode('utf-8') - }) + cache_key += '.' + urlencode({get_parameter: str(parameter_value).encode('utf-8')}) response = cache.get(cache_key) - if response: - # A hack to ensure that the response data is a valid text type for both Python 2 and 3. - response_content = list(response._container) # lint-amnesty, pylint: disable=bad-option-value, protected-access, protected-member - response.content = b'' - for item in response_content: - response.write(item) + # Ensure that response content is properly handled for caching + response.content = ( + # pylint: disable=protected-access + b''.join(response._container) if hasattr(response, '_container') else response.content + ) else: response = view_func(request, *args, **kwargs) cache.set(cache_key, response, 60 * 3) return response - else: # Don't use the cache. return view_func(request, *args, **kwargs) diff --git a/common/static/data/geoip/GeoLite2-Country.mmdb b/common/static/data/geoip/GeoLite2-Country.mmdb index 479048791da5..4404d374206e 100644 Binary files a/common/static/data/geoip/GeoLite2-Country.mmdb and b/common/static/data/geoip/GeoLite2-Country.mmdb differ diff --git a/common/static/js/src/tooltip_manager.js b/common/static/js/src/tooltip_manager.js index d3fe0d1566e7..71db826a705d 100644 --- a/common/static/js/src/tooltip_manager.js +++ b/common/static/js/src/tooltip_manager.js @@ -32,9 +32,20 @@ }, getCoords: function(pageX, pageY) { + let left = pageX - 0.5 * this.tooltip.outerWidth(); + const top = pageY - (this.tooltip.outerHeight() + 15); + // Check if the tooltip is going off the right edge of the screen + if (left + this.tooltip.outerWidth() > window.innerWidth) { + left = window.innerWidth - this.tooltip.outerWidth(); + } + // Check if the tooltip is going off the left edge of the screen + if (left < 0) { + left = 0; + } + return { - left: pageX - 0.5 * this.tooltip.outerWidth(), - top: pageY - (this.tooltip.outerHeight() + 15) + left, + top, }; }, diff --git a/docs/conf.py b/docs/conf.py index 7352195cbb11..604ca3755429 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,7 +55,6 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', 'sphinx.ext.coverage', 'sphinx.ext.doctest', 'sphinx.ext.graphviz', @@ -68,6 +67,18 @@ 'sphinx_design', 'code_annotations.contrib.sphinx.extensions.featuretoggles', 'code_annotations.contrib.sphinx.extensions.settings', + 'autoapi.extension', +] + +autoapi_type = 'python' +autoapi_dirs = ['../lms', '../openedx'] + +autoapi_ignore = [ + '*/migrations/*', + '*/tests/*', + '*.pyc', + '__init__.py', + '**/xblock_serializer/data.py', ] # Rediraffe related settings. @@ -277,13 +288,6 @@ 'django': ('https://docs.djangoproject.com/en/1.11/', 'https://docs.djangoproject.com/en/1.11/_objects/'), } -# Mock out these external modules during code import to avoid errors -autodoc_mock_imports = [ - 'MySQLdb', - 'django_mysql', - 'pymongo', -] - # Start building a map of the directories relative to the repository root to # run sphinx-apidoc against and the directories under "docs" in which to store # the generated *.rst files diff --git a/docs/docs_settings.py b/docs/docs_settings.py index f791b2faafb9..d5164edfa1e1 100644 --- a/docs/docs_settings.py +++ b/docs/docs_settings.py @@ -37,6 +37,7 @@ "cms.djangoapps.course_creators", "cms.djangoapps.xblock_config.apps.XBlockConfig", "lms.djangoapps.lti_provider", + "openedx.core.djangoapps.content.search", ] ) diff --git a/docs/lms-openapi.yaml b/docs/lms-openapi.yaml index 8011f84b4573..4d318f8a2b62 100644 --- a/docs/lms-openapi.yaml +++ b/docs/lms-openapi.yaml @@ -9291,21 +9291,6 @@ paths: in: path required: true type: string - /user/v1/skill_level/{job_id}/: - get: - operationId: user_v1_skill_level_read - description: GET /api/user/v1/skill_level/{job_id}/ - parameters: [] - responses: - '200': - description: '' - tags: - - user - parameters: - - name: job_id - in: path - required: true - type: string /user/v1/user_prefs/: get: operationId: user_v1_user_prefs_list diff --git a/lms/djangoapps/ccx/tests/test_field_override_performance.py b/lms/djangoapps/ccx/tests/test_field_override_performance.py index 68926a24f2dd..a7128495eb59 100644 --- a/lms/djangoapps/ccx/tests/test_field_override_performance.py +++ b/lms/djangoapps/ccx/tests/test_field_override_performance.py @@ -234,7 +234,7 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase): __test__ = True # TODO: decrease query count as part of REVO-28 - QUERY_COUNT = 33 + QUERY_COUNT = 34 TEST_DATA = { ('no_overrides', 1, True, False): (QUERY_COUNT, 2), diff --git a/lms/djangoapps/certificates/api.py b/lms/djangoapps/certificates/api.py index bd7db8662e70..895822f58824 100644 --- a/lms/djangoapps/certificates/api.py +++ b/lms/djangoapps/certificates/api.py @@ -82,6 +82,7 @@ def _format_certificate_for_user(username, cert): if cert.status == CertificateStatuses.downloadable else None ), + "uuid": cert.verify_uuid, } return None diff --git a/lms/djangoapps/certificates/apis/v0/tests/test_views.py b/lms/djangoapps/certificates/apis/v0/tests/test_views.py index efff97f54d5a..c8ef620dd2db 100644 --- a/lms/djangoapps/certificates/apis/v0/tests/test_views.py +++ b/lms/djangoapps/certificates/apis/v0/tests/test_views.py @@ -173,17 +173,22 @@ def get_url(self, username): def assert_success_response_for_student(self, response, download_url='www.google.com'): """ This method is required by AuthAndScopesTestMixin. """ - assert response.data ==\ - [{'username': self.student.username, - 'course_id': str(self.course.id), - 'course_display_name': self.course.display_name, - 'course_organization': self.course.org, - 'certificate_type': CourseMode.VERIFIED, - 'created_date': self.now, - 'modified_date': self.now, - 'status': CertificateStatuses.downloadable, - 'is_passing': True, - 'download_url': download_url, 'grade': '0.88'}] + assert response.data == [ + { + 'username': self.student.username, + 'course_id': str(self.course.id), + 'course_display_name': self.course.display_name, + 'course_organization': self.course.org, + 'certificate_type': CourseMode.VERIFIED, + 'created_date': self.now, + 'modified_date': self.now, + 'status': CertificateStatuses.downloadable, + 'is_passing': True, + 'download_url': download_url, + 'grade': '0.88', + 'uuid': str(self.cert.verify_uuid) + } + ] @patch('edx_rest_framework_extensions.permissions.log') @ddt.data(*list(AuthType)) @@ -212,6 +217,7 @@ def test_another_user_with_certs_shared_public(self, auth_type): assert resp.status_code == status.HTTP_200_OK assert len(resp.data) == 1 + assert 'uuid' in resp.data[0] def test_owner_can_access_its_certs(self): """ @@ -227,6 +233,7 @@ def test_owner_can_access_its_certs(self): resp = self.get_response(AuthType.session, requesting_user=self.student) assert resp.status_code == status.HTTP_200_OK + assert 'uuid' in resp.data[0] # verifies that other than owner cert list api is not accessible resp = self.get_response(AuthType.session, requesting_user=self.other_student) @@ -246,12 +253,15 @@ def test_public_profile_certs_is_accessible(self): resp = self.get_response(AuthType.session, requesting_user=self.student) assert resp.status_code == status.HTTP_200_OK + assert 'uuid' in resp.data[0] resp = self.get_response(AuthType.session, requesting_user=self.other_student) assert resp.status_code == status.HTTP_200_OK + assert 'uuid' in resp.data[0] resp = self.get_response(AuthType.session, requesting_user=self.global_staff) assert resp.status_code == status.HTTP_200_OK + assert 'uuid' in resp.data[0] @ddt.data(*list(AuthType)) def test_another_user_with_certs_shared_custom(self, auth_type): @@ -276,6 +286,7 @@ def test_another_user_with_certs_shared_custom(self, auth_type): assert resp.status_code == status.HTTP_200_OK assert len(resp.data) == 1 + assert 'uuid' in resp.data[0] @patch('edx_rest_framework_extensions.permissions.log') @ddt.data(*JWT_AUTH_TYPES) @@ -290,6 +301,7 @@ def test_jwt_on_behalf_of_other_user(self, auth_type, mock_log): else: assert resp.status_code == status.HTTP_200_OK assert len(resp.data) == 1 + assert 'uuid' in resp.data[0] @patch('edx_rest_framework_extensions.permissions.log') @ddt.data(*JWT_AUTH_TYPES) @@ -422,3 +434,4 @@ def test_certificate_without_course(self, mock_get_course_run_details): assert response.status_code == status.HTTP_200_OK self.assertContains(response, cert_for_deleted_course.download_url) self.assertContains(response, expected_course_name) + assert 'uuid' in response.data[0] diff --git a/lms/djangoapps/certificates/apis/v0/views.py b/lms/djangoapps/certificates/apis/v0/views.py index 121f37fe72d0..aa6512535b84 100644 --- a/lms/djangoapps/certificates/apis/v0/views.py +++ b/lms/djangoapps/certificates/apis/v0/views.py @@ -244,6 +244,7 @@ def get(self, request, username): 'is_passing': user_cert.get('is_passing'), 'download_url': user_cert.get('download_url'), 'grade': user_cert.get('grade'), + 'uuid': user_cert.get('uuid'), }) return Response(user_certs) diff --git a/lms/djangoapps/course_api/blocks/tests/test_api.py b/lms/djangoapps/course_api/blocks/tests/test_api.py index e6fd74463aa9..93e400d4a804 100644 --- a/lms/djangoapps/course_api/blocks/tests/test_api.py +++ b/lms/djangoapps/course_api/blocks/tests/test_api.py @@ -3,16 +3,13 @@ """ -from itertools import product from unittest.mock import patch import ddt from django.test.client import RequestFactory -from edx_toggles.toggles.testutils import override_waffle_switch from common.djangoapps.student.tests.factories import UserFactory from openedx.core.djangoapps.content.block_structure.api import clear_course_from_cache -from openedx.core.djangoapps.content.block_structure.config import STORAGE_BACKING_FOR_CACHE from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.tests.factories import SampleCourseFactory, check_mongo_calls # lint-amnesty, pylint: disable=wrong-import-order @@ -209,34 +206,25 @@ class TestGetBlocksQueryCounts(TestGetBlocksQueryCountsBase): Tests query counts for the get_blocks function. """ - @ddt.data( - *product( - (ModuleStoreEnum.Type.split, ), - (True, False), + @ddt.data(ModuleStoreEnum.Type.split) + def test_query_counts_cached(self, store_type): + course = self._create_course(store_type) + self._get_blocks( + course, + expected_mongo_queries=0, + expected_sql_queries=14, ) - ) - @ddt.unpack - def test_query_counts_cached(self, store_type, with_storage_backing): - with override_waffle_switch(STORAGE_BACKING_FOR_CACHE, active=with_storage_backing): - course = self._create_course(store_type) - self._get_blocks( - course, - expected_mongo_queries=0, - expected_sql_queries=14 if with_storage_backing else 13, - ) @ddt.data( - (ModuleStoreEnum.Type.split, 2, True, 24), - (ModuleStoreEnum.Type.split, 2, False, 14), + (ModuleStoreEnum.Type.split, 2, 24), ) @ddt.unpack - def test_query_counts_uncached(self, store_type, expected_mongo_queries, with_storage_backing, num_sql_queries): - with override_waffle_switch(STORAGE_BACKING_FOR_CACHE, active=with_storage_backing): - course = self._create_course(store_type) - clear_course_from_cache(course.id) - - self._get_blocks( - course, - expected_mongo_queries, - expected_sql_queries=num_sql_queries, - ) + def test_query_counts_uncached(self, store_type, expected_mongo_queries, num_sql_queries): + course = self._create_course(store_type) + clear_course_from_cache(course.id) + + self._get_blocks( + course, + expected_mongo_queries, + expected_sql_queries=num_sql_queries, + ) diff --git a/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py b/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py index e87e0e755685..5efea79c1986 100644 --- a/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py +++ b/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py @@ -13,6 +13,7 @@ from lms.djangoapps.course_blocks.api import get_course_blocks from lms.djangoapps.course_blocks.transformers.tests.helpers import CourseStructureTestCase from lms.djangoapps.gating import api as lms_gating_api +import openedx.core.djangoapps.content.block_structure.api as bs_api from openedx.core.djangoapps.content.block_structure.transformers import BlockStructureTransformers from openedx.core.djangoapps.course_apps.toggles import EXAMS_IDA from openedx.core.lib.gating import api as gating_api @@ -166,7 +167,11 @@ def test_gated(self, gated_block_ref, gating_block_ref, expected_blocks_before_c self.course.enable_subsection_gating = True self.setup_gated_section(self.blocks[gated_block_ref], self.blocks[gating_block_ref]) - with self.assertNumQueries(5): + # Cache the course blocks so that they don't need to be generated when we're trying to + # get data back. This would happen as a part of publishing in a production system. + bs_api.update_course_in_cache(self.course.id) + + with self.assertNumQueries(4): self.get_blocks_and_check_against_expected(self.user, expected_blocks_before_completion) # clear the request cache to simulate a new request @@ -179,7 +184,7 @@ def test_gated(self, gated_block_ref, gating_block_ref, expected_blocks_before_c self.user, ) - with self.assertNumQueries(6): + with self.assertNumQueries(4): self.get_blocks_and_check_against_expected(self.user, self.ALL_BLOCKS_EXCEPT_SPECIAL) def test_staff_access(self): diff --git a/lms/djangoapps/course_blocks/api.py b/lms/djangoapps/course_blocks/api.py index c978f54e02d5..de4e0443fb08 100644 --- a/lms/djangoapps/course_blocks/api.py +++ b/lms/djangoapps/course_blocks/api.py @@ -108,4 +108,5 @@ def get_course_blocks( transformers, starting_block_usage_key, collected_block_structure, + user, ) diff --git a/lms/djangoapps/course_blocks/tests/test_api.py b/lms/djangoapps/course_blocks/tests/test_api.py new file mode 100644 index 000000000000..52e9c86ec324 --- /dev/null +++ b/lms/djangoapps/course_blocks/tests/test_api.py @@ -0,0 +1,149 @@ +# pylint: disable=attribute-defined-outside-init +""" +Tests for course_blocks API +""" + +from unittest.mock import Mock, patch + +import ddt +from django.http.request import HttpRequest + +from common.djangoapps.student.tests.factories import UserFactory +from lms.djangoapps.course_blocks.api import get_course_blocks +from lms.djangoapps.course_blocks.transformers.tests.helpers import CourseStructureTestCase +from lms.djangoapps.course_blocks.transformers.tests.test_user_partitions import UserPartitionTestMixin +from lms.djangoapps.courseware.block_render import make_track_function, prepare_runtime_for_user +from openedx.core.djangoapps.content.block_structure.transformers import BlockStructureTransformers +from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort +from xmodule.modulestore.django import modulestore + + +def get_block_side_effect(block_locator, user_known): + """ + Side effect for `CachingDescriptorSystem.get_block` + """ + store = modulestore() + course = store.get_course(block_locator.course_key) + block = store.get_item(block_locator) + runtime = block.runtime + user = UserFactory.create() + user.known = user_known + + prepare_runtime_for_user( + user=user, + student_data=Mock(), + runtime=runtime, + course_id=block_locator.course_key, + track_function=make_track_function(HttpRequest()), + request_token=Mock(), + course=course, + ) + return block.runtime.get_block_for_descriptor(block) + + +def get_block_side_effect_for_known_user(self, *args, **kwargs): + """ + Side effect for known user test. + """ + return get_block_side_effect(self, True) + + +def get_block_side_effect_for_unknown_user(self, *args, **kwargs): + """ + Side effect for unknown user test. + """ + return get_block_side_effect(self, False) + + +@ddt.ddt +class TestGetCourseBlocks(UserPartitionTestMixin, CourseStructureTestCase): + """ + Tests `get_course_blocks` API + """ + + def setup_partitions_and_course(self): + """ + Setup course structure. + """ + # Set up user partitions and groups. + self.setup_groups_partitions(active=True, num_groups=1) + self.user_partition = self.user_partitions[0] + + # Build course. + self.course_hierarchy = self.get_course_hierarchy() + self.blocks = self.build_course(self.course_hierarchy) + self.course = self.blocks['course'] + + # Set up cohorts. + self.setup_cohorts(self.course) + + def get_course_hierarchy(self): + """ + Returns a course hierarchy to test with. + """ + # course + # / \ + # / \ + # A[0] B + # | + # | + # O + + return [ + { + 'org': 'UserPartitionTransformer', + 'course': 'UP101F', + 'run': 'test_run', + 'user_partitions': [self.user_partition], + '#type': 'course', + '#ref': 'course', + '#children': [ + { + '#type': 'vertical', + '#ref': 'A', + 'metadata': {'group_access': {self.user_partition.id: [0]}}, + }, + {'#type': 'vertical', '#ref': 'B'}, + ], + }, + { + '#type': 'vertical', + '#ref': 'O', + '#parents': ['B'], + }, + ] + + @ddt.data( + (1, ('course', 'B', 'O'), True), + (1, ('course', 'A', 'B', 'O'), False), + (None, ('course', 'B', 'O'), True), + (None, ('course', 'A', 'B', 'O'), False), + ) + @ddt.unpack + def test_get_course_blocks(self, group_id, expected_blocks, user_known): + """ + Tests that `get_course_blocks` returns blocks without access checks for unknown users. + + Access checks are done through the transformers and through Runtime get_block_for_descriptor. Due + to the runtime limitations during the tests, the Runtime access checks are not performed as + get_block_for_descriptor is never called and Block is returned by CachingDescriptorSystem.get_block. + In this test, we mock the CachingDescriptorSystem.get_block and check block access for known and unknown users. + For known users, it performs the Runtime access checks through get_block_for_descriptor. For unknown, it + skips the access checks. + """ + self.setup_partitions_and_course() + if group_id: + cohort = self.partition_cohorts[self.user_partition.id - 1][group_id - 1] + add_user_to_cohort(cohort, self.user.username) + + side_effect = get_block_side_effect_for_known_user if user_known else get_block_side_effect_for_unknown_user + with patch('xmodule.modulestore.split_mongo.split.CachingDescriptorSystem.get_block', side_effect=side_effect): + block_structure = get_course_blocks( + self.user, + self.course.location, + BlockStructureTransformers([]), + ) + self.assertSetEqual( + set(block_structure.get_block_keys()), + self.get_block_key_set(self.blocks, *expected_blocks) + ) diff --git a/lms/djangoapps/course_blocks/transformers/tests/test_library_content.py b/lms/djangoapps/course_blocks/transformers/tests/test_library_content.py index 7fdb58f7f612..5a4d7a0de11a 100644 --- a/lms/djangoapps/course_blocks/transformers/tests/test_library_content.py +++ b/lms/djangoapps/course_blocks/transformers/tests/test_library_content.py @@ -8,6 +8,7 @@ from openedx.core.djangoapps.content.block_structure.api import clear_course_from_cache from openedx.core.djangoapps.content.block_structure.transformers import BlockStructureTransformers +import openedx.core.djangoapps.content.block_structure.api as bs_api from ...api import get_course_blocks from ..library_content import ContentLibraryOrderTransformer, ContentLibraryTransformer from .helpers import CourseStructureTestCase @@ -41,6 +42,8 @@ def setUp(self): self.course_hierarchy = self.get_course_hierarchy() self.blocks = self.build_course(self.course_hierarchy) self.course = self.blocks['course'] + # Do this manually because publish signals are not fired by default in tests. + bs_api.update_course_in_cache(self.course.id) clear_course_from_cache(self.course.id) # Enroll user in course. @@ -122,6 +125,7 @@ def test_content_library(self): ) assert len(list(raw_block_structure.get_block_keys())) == len(self.blocks) + bs_api.update_course_in_cache(self.course.id) clear_course_from_cache(self.course.id) trans_block_structure = get_course_blocks( self.user, @@ -175,6 +179,7 @@ def setUp(self): self.course_hierarchy = self.get_course_hierarchy() self.blocks = self.build_course(self.course_hierarchy) self.course = self.blocks['course'] + bs_api.update_course_in_cache(self.course.id) clear_course_from_cache(self.course.id) # Enroll user in course. diff --git a/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py b/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py index abd1dc53755b..b49d79976c06 100644 --- a/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py +++ b/lms/djangoapps/course_goals/management/commands/goal_reminder_email.py @@ -2,7 +2,9 @@ Command to trigger sending reminder emails for learners to achieve their Course Goals """ from datetime import date, datetime, timedelta +from eventtracking import tracker import logging +import uuid from django.conf import settings from django.contrib.sites.models import Site @@ -32,7 +34,7 @@ SUNDAY_WEEKDAY = 6 -def send_ace_message(goal): +def send_ace_message(goal, session_id): """ Send an email reminding users to stay on track for their learning goal in this course @@ -46,6 +48,15 @@ def send_ace_message(goal): course = CourseOverview.get_from_id(goal.course_key) except CourseOverview.DoesNotExist: log.error(f"Goal Reminder course {goal.course_key} not found.") + tracker.emit( + 'edx.course.goal.email.failed', + { + 'uuid': session_id, + 'timestamp': datetime.now(), + 'reason': 'course not found', + 'course_key': goal.course_key, + } + ) return False course_name = course.display_name @@ -111,6 +122,15 @@ def send_ace_message(goal): ace.send(msg) except Exception as exc: # pylint: disable=broad-except log.error(f"Goal Reminder for {user.id} for course {goal.course_key} could not send: {exc}") + tracker.emit( + 'edx.course.goal.email.failed', + { + 'uuid': session_id, + 'timestamp': datetime.now(), + 'reason': 'ace error', + 'error': str(exc), + } + ) return False return True @@ -132,8 +152,16 @@ def handle(self, *args, **options): try: self._handle_all_goals() - except BaseException: # pylint: disable=broad-except + except BaseException as exc: # pylint: disable=broad-except log.exception("Error while sending course goals emails: ") + tracker.emit( + 'edx.course.goal.email.failed', + { + 'timestamp': datetime.now(), + 'reason': 'base exception', + 'error': str(exc), + } + ) for h in log.handlers: h.flush() raise @@ -148,6 +176,7 @@ def _handle_all_goals(self): today = date.today() sunday_date = today + timedelta(days=SUNDAY_WEEKDAY - today.weekday()) monday_date = today - timedelta(days=today.weekday()) + session_id = str(uuid.uuid4()) # Monday is the start of when we consider user's activity towards counting towards their weekly # goal. As such, we use Mondays to clear out the email reminders sent from the previous week. @@ -174,21 +203,42 @@ def _handle_all_goals(self): filtered_count = 0 course_goals = course_goals.exclude(course_key__in=courses_to_exclude).select_related('user').order_by('user') total_goals = len(course_goals) - log.info(f'Processing course goals, total goal count {total_goals}') + tracker.emit( + 'edx.course.goal.email.session_started', + { + 'uuid': session_id, + 'timestamp': datetime.now(), + 'goal_count': total_goals, + } + ) + log.info(f'Processing course goals, total goal count {total_goals},' + + f'timestamp: {datetime.now()}, uuid: {session_id}') for goal in course_goals: # emulate a request for waffle's benefit with emulate_http_request(site=Site.objects.get_current(), user=goal.user): - if self.handle_goal(goal, today, sunday_date, monday_date): + if self.handle_goal(goal, today, sunday_date, monday_date, session_id): sent_count += 1 else: filtered_count += 1 if (sent_count + filtered_count) % 10000 == 0: - log.info(f'Processing course goals: sent {sent_count} filtered {filtered_count} out of {total_goals}') - - log.info(f'Processing course goals complete: sent {sent_count} emails, filtered out {filtered_count} emails') + log.info(f'Processing course goals: sent {sent_count} filtered {filtered_count} out of {total_goals},' + + f'timestamp: {datetime.now()}, uuid: {session_id}') + + tracker.emit( + 'edx.course.goal.email.session_completed', + { + 'uuid': session_id, + 'timestamp': datetime.now(), + 'goal_count': total_goals, + 'emails_sent': sent_count, + 'emails_filtered': filtered_count, + } + ) + log.info(f'Processing course goals complete: sent {sent_count} emails, filtered out {filtered_count} emails' + + f'timestamp: {datetime.now()}, uuid: {session_id}') @staticmethod - def handle_goal(goal, today, sunday_date, monday_date): + def handle_goal(goal, today, sunday_date, monday_date, session_id): """Sends an email reminder for a single CourseGoal, if it passes all our checks""" if not ENABLE_COURSE_GOALS.is_enabled(goal.course_key): return False @@ -223,10 +273,20 @@ def handle_goal(goal, today, sunday_date, monday_date): user_timezone = get_user_timezone_or_last_seen_timezone_or_utc(goal.user) now_in_users_timezone = datetime.now(user_timezone) if not 8 <= now_in_users_timezone.hour < 18: + tracker.emit( + 'edx.course.goal.email.filtered', + { + 'uuid': session_id, + 'timestamp': datetime.now(), + 'reason': 'User time zone', + 'user_timezone': user_timezone, + 'now_in_users_timezone': now_in_users_timezone, + } + ) return False if required_days_left == days_left_in_week: - sent = send_ace_message(goal) + sent = send_ace_message(goal, session_id) if sent: CourseGoalReminderStatus.objects.update_or_create(goal=goal, defaults={'email_reminder_sent': True}) return True diff --git a/lms/djangoapps/course_goals/models.py b/lms/djangoapps/course_goals/models.py index 73d69d6bd6ce..616966600040 100644 --- a/lms/djangoapps/course_goals/models.py +++ b/lms/djangoapps/course_goals/models.py @@ -79,6 +79,8 @@ class CourseGoalReminderStatus(TimeStampedModel): Tracks whether we've sent a reminder about a particular goal this week. See the management command goal_reminder_email for more detail about how this is used. + + .. no_pii: """ class Meta: verbose_name_plural = "Course goal reminder statuses" diff --git a/lms/djangoapps/course_home_api/models.py b/lms/djangoapps/course_home_api/models.py index 62d1e0fda36d..94c79b8760c8 100644 --- a/lms/djangoapps/course_home_api/models.py +++ b/lms/djangoapps/course_home_api/models.py @@ -11,6 +11,8 @@ class DisableProgressPageStackedConfig(StackedConfigurationModel): """ Stacked Config Model for disabling the frontend-app-learning progress page + + .. no_pii: """ STACKABLE_FIELDS = ('disabled',) diff --git a/lms/djangoapps/courseware/management/commands/import.py b/lms/djangoapps/courseware/management/commands/import.py deleted file mode 100644 index 7ebb26795112..000000000000 --- a/lms/djangoapps/courseware/management/commands/import.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -Script for importing courseware from XML format -""" - - -from django.core.management.base import BaseCommand - -from openedx.core.djangoapps.django_comment_common.utils import are_permissions_roles_seeded, seed_permissions_roles -from xmodule.contentstore.django import contentstore # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.modulestore.xml_importer import import_course_from_xml # lint-amnesty, pylint: disable=wrong-import-order -from xmodule.util.sandboxing import DEFAULT_PYTHON_LIB_FILENAME # lint-amnesty, pylint: disable=wrong-import-order - - -class Command(BaseCommand): - """ - Import the specified data directory into the default ModuleStore - """ - help = 'Import the specified data directory into the default ModuleStore.' - - def add_arguments(self, parser): - parser.add_argument('data_directory') - parser.add_argument('course_dirs', - nargs='*', - metavar='course_dir') - parser.add_argument('--nostatic', - action='store_true', - help='Skip import of static content') - parser.add_argument('--nopythonlib', - action='store_true', - help=( - 'Skip import of course python library if it exists ' - '(NOTE: If the static content import is not skipped, the python library ' - 'will be imported and this flag will be ignored)' - )) - parser.add_argument('--python-lib-filename', - default=DEFAULT_PYTHON_LIB_FILENAME, - help='Filename of the course code library (if it exists)') - - def handle(self, *args, **options): - data_dir = options['data_directory'] - source_dirs = options['course_dirs'] - if not source_dirs: - source_dirs = None - do_import_static = not options.get('nostatic', False) - # If the static content is not skipped, the python lib should be imported regardless - # of the 'nopythonlib' flag. - do_import_python_lib = do_import_static or not options.get('nopythonlib', False) - python_lib_filename = options.get('python_lib_filename') - - output = ( - "Importing...\n" - " data_dir={data}, source_dirs={courses}\n" - " Importing static content? {import_static}\n" - " Importing python lib? {import_python_lib}" - ).format( - data=data_dir, - courses=source_dirs, - import_static=do_import_static, - import_python_lib=do_import_python_lib - ) - self.stdout.write(output) - mstore = modulestore() - - course_items = import_course_from_xml( - mstore, ModuleStoreEnum.UserID.mgmt_command, data_dir, source_dirs, load_error_blocks=False, - static_content_store=contentstore(), verbose=True, - do_import_static=do_import_static, do_import_python_lib=do_import_python_lib, - create_if_not_present=True, - python_lib_filename=python_lib_filename, - ) - - for course in course_items: - course_id = course.id - if not are_permissions_roles_seeded(course_id): - self.stdout.write(f'Seeding forum roles for course {course_id}\n') - seed_permissions_roles(course_id) diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py index b5cd3839c351..eacf2424de6c 100644 --- a/lms/djangoapps/courseware/models.py +++ b/lms/djangoapps/courseware/models.py @@ -545,6 +545,8 @@ class Meta: class FinancialAssistanceConfiguration(ConfigurationModel): """ Manages configuration for connecting to Financial Assistance backend service and using its API. + + .. no_pii: """ api_base_url = models.URLField( diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 7a5e36e54904..126233880ae1 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -132,38 +132,6 @@ class TestJumpTo(ModuleStoreTestCase): """ Check the jumpto link for a course. """ - @ddt.data( - (True, False), # preview -> Legacy experience - (False, True), # no preview -> MFE experience - ) - @ddt.unpack - def test_jump_to_legacy_vs_mfe(self, preview_mode, expect_mfe): - """ - Test that jump_to and jump_to_id correctly choose which courseware frontend to redirect to. - - Can be removed when the MFE supports a preview mode. - """ - course = CourseFactory.create() - chapter = BlockFactory.create(category='chapter', parent_location=course.location) - if expect_mfe: - expected_url = f'http://learning-mfe/course/{course.id}/{chapter.location}' - else: - expected_url = f'/courses/{course.id}/courseware/{chapter.url_name}/' - - jumpto_url = f'/courses/{course.id}/jump_to/{chapter.location}' - with set_preview_mode(preview_mode): - response = self.client.get(jumpto_url) - assert response.status_code == 302 - # Check the response URL, but chop off the querystring; we don't care here. - assert response.url.split('?')[0] == expected_url - - jumpto_id_url = f'/courses/{course.id}/jump_to_id/{chapter.url_name}' - with set_preview_mode(preview_mode): - response = self.client.get(jumpto_id_url) - assert response.status_code == 302 - # Check the response URL, but chop off the querystring; we don't care here. - assert response.url.split('?')[0] == expected_url - @ddt.data( (False, ModuleStoreEnum.Type.split), (True, ModuleStoreEnum.Type.split), @@ -174,32 +142,34 @@ def test_jump_to_invalid_location(self, preview_mode, store_type): with self.store.default_store(store_type): course = CourseFactory.create() location = course.id.make_usage_key(None, 'NoSuchPlace') - expected_redirect_url = ( - f'/courses/{course.id}/courseware?' + urlencode({'activate_block_id': str(course.location)}) + + expected_redirect_url = f'http://learning-mfe/course/{course.id}' + jumpto_url = ( + f'/courses/{course.id}/jump_to/{location}?preview=1' ) if preview_mode else ( - f'http://learning-mfe/course/{course.id}' + f'/courses/{course.id}/jump_to/{location}' ) + # This is fragile, but unfortunately the problem is that within the LMS we # can't use the reverse calls from the CMS - jumpto_url = f'/courses/{course.id}/jump_to/{location}' - with set_preview_mode(preview_mode): + with set_preview_mode(False): response = self.client.get(jumpto_url) assert response.status_code == 302 assert response.url == expected_redirect_url - @set_preview_mode(True) - def test_jump_to_legacy_from_sequence(self): + @set_preview_mode(False) + def test_jump_to_preview_from_sequence(self): with self.store.default_store(ModuleStoreEnum.Type.split): course = CourseFactory.create() chapter = BlockFactory.create(category='chapter', parent_location=course.location) sequence = BlockFactory.create(category='sequential', parent_location=chapter.location) - activate_block_id = urlencode({'activate_block_id': str(sequence.location)}) + jumpto_url = f'/courses/{course.id}/jump_to/{sequence.location}?preview=1' expected_redirect_url = ( - f'/courses/{course.id}/courseware/{chapter.url_name}/{sequence.url_name}/?{activate_block_id}' + f'http://learning-mfe/preview/course/{course.id}/{sequence.location}' ) - jumpto_url = f'/courses/{course.id}/jump_to/{sequence.location}' response = self.client.get(jumpto_url) - self.assertRedirects(response, expected_redirect_url, status_code=302, target_status_code=302) + assert response.status_code == 302 + assert response.url == expected_redirect_url @set_preview_mode(False) def test_jump_to_mfe_from_sequence(self): @@ -214,8 +184,8 @@ def test_jump_to_mfe_from_sequence(self): assert response.status_code == 302 assert response.url == expected_redirect_url - @set_preview_mode(True) - def test_jump_to_legacy_from_block(self): + @set_preview_mode(False) + def test_jump_to_preview_from_block(self): with self.store.default_store(ModuleStoreEnum.Type.split): course = CourseFactory.create() chapter = BlockFactory.create(category='chapter', parent_location=course.location) @@ -225,21 +195,21 @@ def test_jump_to_legacy_from_block(self): block1 = BlockFactory.create(category='html', parent_location=vertical1.location) block2 = BlockFactory.create(category='html', parent_location=vertical2.location) - activate_block_id = urlencode({'activate_block_id': str(block1.location)}) + jumpto_url = f'/courses/{course.id}/jump_to/{block1.location}?preview=1' expected_redirect_url = ( - f'/courses/{course.id}/courseware/{chapter.url_name}/{sequence.url_name}/1?{activate_block_id}' + f'http://learning-mfe/preview/course/{course.id}/{sequence.location}/{vertical1.location}' ) - jumpto_url = f'/courses/{course.id}/jump_to/{block1.location}' response = self.client.get(jumpto_url) - self.assertRedirects(response, expected_redirect_url, status_code=302, target_status_code=302) + assert response.status_code == 302 + assert response.url == expected_redirect_url - activate_block_id = urlencode({'activate_block_id': str(block2.location)}) + jumpto_url = f'/courses/{course.id}/jump_to/{block2.location}?preview=1' expected_redirect_url = ( - f'/courses/{course.id}/courseware/{chapter.url_name}/{sequence.url_name}/2?{activate_block_id}' + f'http://learning-mfe/preview/course/{course.id}/{sequence.location}/{vertical2.location}' ) - jumpto_url = f'/courses/{course.id}/jump_to/{block2.location}' response = self.client.get(jumpto_url) - self.assertRedirects(response, expected_redirect_url, status_code=302, target_status_code=302) + assert response.status_code == 302 + assert response.url == expected_redirect_url @set_preview_mode(False) def test_jump_to_mfe_from_block(self): @@ -300,8 +270,12 @@ def test_jump_to_legacy_from_nested_block(self): def test_jump_to_id_invalid_location(self, preview_mode, store_type): with self.store.default_store(store_type): course = CourseFactory.create() - jumpto_url = f'/courses/{course.id}/jump_to/NoSuchPlace' - with set_preview_mode(preview_mode): + jumpto_url = ( + f'/courses/{course.id}/jump_to/NoSuchPlace?preview=1' + ) if preview_mode else ( + f'/courses/{course.id}/jump_to/NoSuchPlace' + ) + with set_preview_mode(False): response = self.client.get(jumpto_url) assert response.status_code == 404 @@ -1498,8 +1472,8 @@ def test_view_certificate_link(self): self.assertContains(resp, "earned a certificate for this course.") @ddt.data( - (True, 53), - (False, 53), + (True, 54), + (False, 54), ) @ddt.unpack def test_progress_queries_paced_courses(self, self_paced, query_count): @@ -1514,13 +1488,13 @@ def test_progress_queries(self): ContentTypeGatingConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1)) self.setup_course() with self.assertNumQueries( - 53, table_ignorelist=QUERY_COUNT_TABLE_IGNORELIST + 54, table_ignorelist=QUERY_COUNT_TABLE_IGNORELIST ), check_mongo_calls(2): self._get_progress_page() for _ in range(2): with self.assertNumQueries( - 38, table_ignorelist=QUERY_COUNT_TABLE_IGNORELIST + 39, table_ignorelist=QUERY_COUNT_TABLE_IGNORELIST ), check_mongo_calls(2): self._get_progress_page() @@ -3359,7 +3333,7 @@ def test_learner_redirect(self): def test_preview_no_redirect(self): __, __, preview_url = self._get_urls() with set_preview_mode(True): - # Previews will not redirect to the mfe + # Previews server from PREVIEW_LMS_BASE will not redirect to the mfe course_staff = UserFactory.create(is_staff=False) CourseStaffRole(self.course_key).add_users(course_staff) self.client.login(username=course_staff.username, password=TEST_PASSWORD) diff --git a/lms/djangoapps/courseware/views/index.py b/lms/djangoapps/courseware/views/index.py index c123c7f71f4c..7a9242f595ef 100644 --- a/lms/djangoapps/courseware/views/index.py +++ b/lms/djangoapps/courseware/views/index.py @@ -27,6 +27,7 @@ from xmodule.course_block import COURSE_VISIBILITY_PUBLIC from xmodule.modulestore.django import modulestore from xmodule.x_module import PUBLIC_VIEW, STUDENT_VIEW +from xmodule.util.xmodule_django import get_current_request_hostname from common.djangoapps.edxmako.shortcuts import render_to_response, render_to_string from common.djangoapps.student.models import CourseEnrollment @@ -188,11 +189,13 @@ def microfrontend_url(self): unit_key = None except InvalidKeyError: unit_key = None + is_preview = settings.FEATURES.get('PREVIEW_LMS_BASE') == get_current_request_hostname() url = make_learning_mfe_courseware_url( self.course_key, self.section.location if self.section else None, unit_key, params=self.request.GET, + preview=is_preview, ) return url diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index b182202eddaf..4fd124e1a47e 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -51,6 +51,7 @@ COURSE_VISIBILITY_PUBLIC_OUTLINE, CATALOG_VISIBILITY_CATALOG_AND_ABOUT, ) +from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem from xmodule.tabs import CourseTabList @@ -439,10 +440,12 @@ def jump_to(request, course_id, location): except InvalidKeyError as exc: raise Http404("Invalid course_key or usage_key") from exc + staff_access = has_access(request.user, 'staff', course_key) try: redirect_url = get_courseware_url( usage_key=usage_key, request=request, + is_staff=staff_access, ) except (ItemNotFoundError, NoPathToItem): # We used to 404 here, but that's ultimately a bad experience. There are real world use cases where a user @@ -452,6 +455,7 @@ def jump_to(request, course_id, location): redirect_url = get_courseware_url( usage_key=course_location_from_key(course_key), request=request, + is_staff=staff_access, ) return redirect(redirect_url) @@ -1565,142 +1569,151 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True, disable_sta f"Rendering of the xblock view '{nh3.clean(requested_view)}' is not supported." ) - staff_access = has_access(request.user, 'staff', course_key) + staff_access = bool(has_access(request.user, 'staff', course_key)) + is_preview = request.GET.get('preview', '0') == '1' - with modulestore().bulk_operations(course_key): - # verify the user has access to the course, including enrollment check - try: - course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=check_if_enrolled) - except CourseAccessRedirect: - raise Http404("Course not found.") # lint-amnesty, pylint: disable=raise-missing-from - - # with course access now verified: - # assume masquerading role, if applicable. - # (if we did this *before* the course access check, then course staff - # masquerading as learners would often be denied access, since course - # staff are generally not enrolled, and viewing a course generally - # requires enrollment.) - _course_masquerade, request.user = setup_masquerade( - request, - course_key, - staff_access, - ) + store = modulestore() + branch_type = ( + ModuleStoreEnum.Branch.draft_preferred + ) if is_preview and staff_access else ( + ModuleStoreEnum.Branch.published_only + ) - # Record user activity for tracking progress towards a user's course goals (for mobile app) - UserActivity.record_user_activity( - request.user, usage_key.course_key, request=request, only_if_mobile_app=True - ) + with store.bulk_operations(course_key): + with store.branch_setting(branch_type, course_key): + # verify the user has access to the course, including enrollment check + try: + course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=check_if_enrolled) + except CourseAccessRedirect: + raise Http404("Course not found.") # lint-amnesty, pylint: disable=raise-missing-from + + # with course access now verified: + # assume masquerading role, if applicable. + # (if we did this *before* the course access check, then course staff + # masquerading as learners would often be denied access, since course + # staff are generally not enrolled, and viewing a course generally + # requires enrollment.) + _course_masquerade, request.user = setup_masquerade( + request, + course_key, + staff_access, + ) - # get the block, which verifies whether the user has access to the block. - recheck_access = request.GET.get('recheck_access') == '1' - block, _ = get_block_by_usage_id( - request, - str(course_key), - str(usage_key), - disable_staff_debug_info=disable_staff_debug_info, - course=course, - will_recheck_access=recheck_access, - ) + # Record user activity for tracking progress towards a user's course goals (for mobile app) + UserActivity.record_user_activity( + request.user, usage_key.course_key, request=request, only_if_mobile_app=True + ) - student_view_context = request.GET.dict() - student_view_context['show_bookmark_button'] = request.GET.get('show_bookmark_button', '0') == '1' - student_view_context['show_title'] = request.GET.get('show_title', '1') == '1' - - is_learning_mfe = is_request_from_learning_mfe(request) - # Right now, we only care about this in regards to the Learning MFE because it results - # in a bad UX if we display blocks with access errors (repeated upgrade messaging). - # If other use cases appear, consider removing the is_learning_mfe check or switching this - # to be its own query parameter that can toggle the behavior. - student_view_context['hide_access_error_blocks'] = is_learning_mfe and recheck_access - is_mobile_app = is_request_from_mobile_app(request) - student_view_context['is_mobile_app'] = is_mobile_app - - enable_completion_on_view_service = False - completion_service = block.runtime.service(block, 'completion') - if completion_service and completion_service.completion_tracking_enabled(): - if completion_service.blocks_to_mark_complete_on_view({block}): - enable_completion_on_view_service = True - student_view_context['wrap_xblock_data'] = { - 'mark-completed-on-view-after-delay': completion_service.get_complete_on_view_delay_ms() - } + # get the block, which verifies whether the user has access to the block. + recheck_access = request.GET.get('recheck_access') == '1' + block, _ = get_block_by_usage_id( + request, + str(course_key), + str(usage_key), + disable_staff_debug_info=disable_staff_debug_info, + course=course, + will_recheck_access=recheck_access, + ) - missed_deadlines, missed_gated_content = dates_banner_should_display(course_key, request.user) - - # Some content gating happens only at the Sequence level (e.g. "has this - # timed exam started?"). - ancestor_sequence_block = enclosing_sequence_for_gating_checks(block) - if ancestor_sequence_block: - context = {'specific_masquerade': is_masquerading_as_specific_student(request.user, course_key)} - # If the SequenceModule feels that gating is necessary, redirect - # there so we can have some kind of error message at any rate. - if ancestor_sequence_block.descendants_are_gated(context): - return redirect( - reverse( - 'render_xblock', - kwargs={'usage_key_string': str(ancestor_sequence_block.location)} + student_view_context = request.GET.dict() + student_view_context['show_bookmark_button'] = request.GET.get('show_bookmark_button', '0') == '1' + student_view_context['show_title'] = request.GET.get('show_title', '1') == '1' + + is_learning_mfe = is_request_from_learning_mfe(request) + # Right now, we only care about this in regards to the Learning MFE because it results + # in a bad UX if we display blocks with access errors (repeated upgrade messaging). + # If other use cases appear, consider removing the is_learning_mfe check or switching this + # to be its own query parameter that can toggle the behavior. + student_view_context['hide_access_error_blocks'] = is_learning_mfe and recheck_access + is_mobile_app = is_request_from_mobile_app(request) + student_view_context['is_mobile_app'] = is_mobile_app + + enable_completion_on_view_service = False + completion_service = block.runtime.service(block, 'completion') + if completion_service and completion_service.completion_tracking_enabled(): + if completion_service.blocks_to_mark_complete_on_view({block}): + enable_completion_on_view_service = True + student_view_context['wrap_xblock_data'] = { + 'mark-completed-on-view-after-delay': completion_service.get_complete_on_view_delay_ms() + } + + missed_deadlines, missed_gated_content = dates_banner_should_display(course_key, request.user) + + # Some content gating happens only at the Sequence level (e.g. "has this + # timed exam started?"). + ancestor_sequence_block = enclosing_sequence_for_gating_checks(block) + if ancestor_sequence_block: + context = {'specific_masquerade': is_masquerading_as_specific_student(request.user, course_key)} + # If the SequenceModule feels that gating is necessary, redirect + # there so we can have some kind of error message at any rate. + if ancestor_sequence_block.descendants_are_gated(context): + return redirect( + reverse( + 'render_xblock', + kwargs={'usage_key_string': str(ancestor_sequence_block.location)} + ) ) - ) - # For courses using an LTI provider managed by edx-exams: - # Access to exam content is determined by edx-exams and passed to the LMS using a - # JWT url param. There is no longer a need for exam gating or logic inside the - # sequence block or its render call. descendants_are_gated shoule not return true - # for these timed exams. Instead, sequences are assumed gated by default and we look for - # an access token on the request to allow rendering to continue. - if course.proctoring_provider == 'lti_external': - seq_block = ancestor_sequence_block if ancestor_sequence_block else block - if getattr(seq_block, 'is_time_limited', None): - if not _check_sequence_exam_access(request, seq_block.location): - return HttpResponseForbidden("Access to exam content is restricted") + # For courses using an LTI provider managed by edx-exams: + # Access to exam content is determined by edx-exams and passed to the LMS using a + # JWT url param. There is no longer a need for exam gating or logic inside the + # sequence block or its render call. descendants_are_gated shoule not return true + # for these timed exams. Instead, sequences are assumed gated by default and we look for + # an access token on the request to allow rendering to continue. + if course.proctoring_provider == 'lti_external': + seq_block = ancestor_sequence_block if ancestor_sequence_block else block + if getattr(seq_block, 'is_time_limited', None): + if not _check_sequence_exam_access(request, seq_block.location): + return HttpResponseForbidden("Access to exam content is restricted") + + context = { + 'course': course, + 'block': block, + 'disable_accordion': True, + 'allow_iframing': True, + 'disable_header': True, + 'disable_footer': True, + 'disable_window_wrap': True, + 'enable_completion_on_view_service': enable_completion_on_view_service, + 'edx_notes_enabled': is_feature_enabled(course, request.user), + 'staff_access': staff_access, + 'xqa_server': settings.FEATURES.get('XQA_SERVER', 'http://your_xqa_server.com'), + 'missed_deadlines': missed_deadlines, + 'missed_gated_content': missed_gated_content, + 'has_ended': course.has_ended(), + 'web_app_course_url': get_learning_mfe_home_url(course_key=course.id, url_fragment='home'), + 'on_courseware_page': True, + 'verified_upgrade_link': verified_upgrade_deadline_link(request.user, course=course), + 'is_learning_mfe': is_learning_mfe, + 'is_mobile_app': is_mobile_app, + 'render_course_wide_assets': True, + } - context = { - 'course': course, - 'block': block, - 'disable_accordion': True, - 'allow_iframing': True, - 'disable_header': True, - 'disable_footer': True, - 'disable_window_wrap': True, - 'enable_completion_on_view_service': enable_completion_on_view_service, - 'edx_notes_enabled': is_feature_enabled(course, request.user), - 'staff_access': staff_access, - 'xqa_server': settings.FEATURES.get('XQA_SERVER', 'http://your_xqa_server.com'), - 'missed_deadlines': missed_deadlines, - 'missed_gated_content': missed_gated_content, - 'has_ended': course.has_ended(), - 'web_app_course_url': get_learning_mfe_home_url(course_key=course.id, url_fragment='home'), - 'on_courseware_page': True, - 'verified_upgrade_link': verified_upgrade_deadline_link(request.user, course=course), - 'is_learning_mfe': is_learning_mfe, - 'is_mobile_app': is_mobile_app, - 'render_course_wide_assets': True, - } + try: + # .. filter_implemented_name: RenderXBlockStarted + # .. filter_type: org.openedx.learning.xblock.render.started.v1 + context, student_view_context = RenderXBlockStarted.run_filter( + context=context, student_view_context=student_view_context + ) + except RenderXBlockStarted.PreventXBlockBlockRender as exc: + log.info("Halted rendering block %s. Reason: %s", usage_key_string, exc.message) + return render_500(request) + except RenderXBlockStarted.RenderCustomResponse as exc: + log.info("Rendering custom exception for block %s. Reason: %s", usage_key_string, exc.message) + context.update({ + 'fragment': Fragment(exc.response) + }) + return render_to_response('courseware/courseware-chromeless.html', context, request=request) + + fragment = block.render(requested_view, context=student_view_context) + optimization_flags = get_optimization_flags_for_content(block, fragment) - try: - # .. filter_implemented_name: RenderXBlockStarted - # .. filter_type: org.openedx.learning.xblock.render.started.v1 - context, student_view_context = RenderXBlockStarted.run_filter( - context=context, student_view_context=student_view_context - ) - except RenderXBlockStarted.PreventXBlockBlockRender as exc: - log.info("Halted rendering block %s. Reason: %s", usage_key_string, exc.message) - return render_500(request) - except RenderXBlockStarted.RenderCustomResponse as exc: - log.info("Rendering custom exception for block %s. Reason: %s", usage_key_string, exc.message) context.update({ - 'fragment': Fragment(exc.response) + 'fragment': fragment, + **optimization_flags, }) - return render_to_response('courseware/courseware-chromeless.html', context, request=request) - fragment = block.render(requested_view, context=student_view_context) - optimization_flags = get_optimization_flags_for_content(block, fragment) - - context.update({ - 'fragment': fragment, - **optimization_flags, - }) - - return render_to_response('courseware/courseware-chromeless.html', context, request=request) + return render_to_response('courseware/courseware-chromeless.html', context, request=request) def get_optimization_flags_for_content(block, fragment): diff --git a/lms/djangoapps/discussion/rest_api/discussions_notifications.py b/lms/djangoapps/discussion/rest_api/discussions_notifications.py index b0eb7c89dcab..929e9917a546 100644 --- a/lms/djangoapps/discussion/rest_api/discussions_notifications.py +++ b/lms/djangoapps/discussion/rest_api/discussions_notifications.py @@ -89,6 +89,7 @@ def _send_course_wide_notification(self, notification_type, audience_filters=Non "post_title": getattr(self.thread, 'title', ''), "course_name": self.course.display_name, "sender_id": self.creator.id, + "group_by_id": str(self.course.id), **extra_context, }, notification_type=notification_type, diff --git a/lms/djangoapps/experiments/models.py b/lms/djangoapps/experiments/models.py index 2e69d185d384..049cd9108292 100644 --- a/lms/djangoapps/experiments/models.py +++ b/lms/djangoapps/experiments/models.py @@ -37,6 +37,7 @@ class ExperimentKeyValue(TimeStampedModel): """ ExperimentData stores any generic key-value associated with experiments identified by experiment_id. + .. no_pii: """ experiment_id = models.PositiveSmallIntegerField( diff --git a/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py b/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py index 5d7c428c2221..42dd9139ee21 100644 --- a/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py +++ b/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py @@ -22,6 +22,7 @@ from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory +import openedx.core.djangoapps.content.block_structure.api as bs_api from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.roles import ( CourseBetaTesterRole, @@ -2228,6 +2229,9 @@ def test_get_override_for_unreleased_block(self): display_name='Unreleased Section', ) + # We need to update the course in the cache after we create the new block. + bs_api.update_course_in_cache(self.course_data.course_key) + resp = self.client.get( self.get_url(subsection_id=unreleased_subsection.location) ) diff --git a/lms/djangoapps/grades/tests/integration/test_events.py b/lms/djangoapps/grades/tests/integration/test_events.py index f058cc81799e..315ffd65ef25 100644 --- a/lms/djangoapps/grades/tests/integration/test_events.py +++ b/lms/djangoapps/grades/tests/integration/test_events.py @@ -7,6 +7,7 @@ from crum import set_current_request +import openedx.core.djangoapps.content.block_structure.api as bs_api from xmodule.capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import UserFactory @@ -76,44 +77,45 @@ def setUp(self): CourseEnrollment.enroll(self.student, self.course.id) self.instructor = UserFactory.create(is_staff=True, username='test_instructor', password=self.TEST_PASSWORD) self.refresh_course() + # Since this doesn't happen automatically and we don't want to run all the publish signal handlers + # Just make sure we have the latest version of the course in cache before we test the problem. + bs_api.update_course_in_cache(self.course.id) @patch('lms.djangoapps.grades.events.tracker') def test_submit_answer(self, events_tracker): self.submit_question_answer('p1', {'2_1': 'choice_choice_2'}) - course = self.store.get_course(self.course.id, depth=0) event_transaction_id = events_tracker.emit.mock_calls[0][1][1]['event_transaction_id'] - events_tracker.emit.assert_has_calls( - [ - mock_call( - events.PROBLEM_SUBMITTED_EVENT_TYPE, - { - 'user_id': str(self.student.id), - 'event_transaction_id': event_transaction_id, - 'event_transaction_type': events.PROBLEM_SUBMITTED_EVENT_TYPE, - 'course_id': str(self.course.id), - 'problem_id': str(self.problem.location), - 'weighted_earned': 2.0, - 'weighted_possible': 2.0, - }, - ), - mock_call( - events.COURSE_GRADE_CALCULATED, - { - 'course_version': str(course.course_version), - 'percent_grade': 0.02, - 'grading_policy_hash': 'ChVp0lHGQGCevD0t4njna/C44zQ=', - 'user_id': str(self.student.id), - 'letter_grade': '', - 'event_transaction_id': event_transaction_id, - 'event_transaction_type': events.PROBLEM_SUBMITTED_EVENT_TYPE, - 'course_id': str(self.course.id), - 'course_edited_timestamp': str(course.subtree_edited_on), - } - ), - ], - any_order=True, - ) + expected_calls = [ + mock_call( + events.PROBLEM_SUBMITTED_EVENT_TYPE, + { + 'user_id': str(self.student.id), + 'event_transaction_id': event_transaction_id, + 'event_transaction_type': events.PROBLEM_SUBMITTED_EVENT_TYPE, + 'course_id': str(self.course.id), + 'problem_id': str(self.problem.location), + 'weighted_earned': 2.0, + 'weighted_possible': 2.0, + }, + ), + mock_call( + events.COURSE_GRADE_CALCULATED, + { + 'course_version': str(self.course.course_version), + 'percent_grade': 0.02, + 'grading_policy_hash': 'ChVp0lHGQGCevD0t4njna/C44zQ=', + 'user_id': str(self.student.id), + 'letter_grade': '', + 'event_transaction_id': event_transaction_id, + 'event_transaction_type': events.PROBLEM_SUBMITTED_EVENT_TYPE, + 'course_id': str(self.course.id), + 'course_edited_timestamp': str(self.course.subtree_edited_on), + } + ), + ] + + events_tracker.emit.assert_has_calls(expected_calls, any_order=True) @ddt.data(True, False) def test_delete_student_state(self, emit_signals): diff --git a/lms/djangoapps/grades/tests/test_course_grade_factory.py b/lms/djangoapps/grades/tests/test_course_grade_factory.py index 2066b6cd0d7c..c47e80a3dae0 100644 --- a/lms/djangoapps/grades/tests/test_course_grade_factory.py +++ b/lms/djangoapps/grades/tests/test_course_grade_factory.py @@ -68,35 +68,35 @@ def _assert_section_order(course_grade): self.sequence2.display_name ] - with self.assertNumQueries(4), mock_get_score(1, 2): + with self.assertNumQueries(5), mock_get_score(1, 2): _assert_read(expected_pass=False, expected_percent=0) # start off with grade of 0 - num_queries = 42 + num_queries = 43 with self.assertNumQueries(num_queries), mock_get_score(1, 2): grade_factory.update(self.request.user, self.course, force_update_subsections=True) - with self.assertNumQueries(3): + with self.assertNumQueries(4): _assert_read(expected_pass=True, expected_percent=0.5) # updated to grade of .5 - num_queries = 6 + num_queries = 7 with self.assertNumQueries(num_queries), mock_get_score(1, 4): grade_factory.update(self.request.user, self.course, force_update_subsections=False) - with self.assertNumQueries(3): + with self.assertNumQueries(4): _assert_read(expected_pass=True, expected_percent=0.5) # NOT updated to grade of .25 - num_queries = 18 + num_queries = 19 with self.assertNumQueries(num_queries), mock_get_score(2, 2): grade_factory.update(self.request.user, self.course, force_update_subsections=True) - with self.assertNumQueries(3): + with self.assertNumQueries(4): _assert_read(expected_pass=True, expected_percent=1.0) # updated to grade of 1.0 - num_queries = 28 + num_queries = 29 with self.assertNumQueries(num_queries), mock_get_score(0, 0): # the subsection now is worth zero grade_factory.update(self.request.user, self.course, force_update_subsections=True) - with self.assertNumQueries(3): + with self.assertNumQueries(4): _assert_read(expected_pass=False, expected_percent=0.0) # updated to grade of 0.0 @ddt.data((True, False)) @@ -286,7 +286,7 @@ def test_grading_exception(self, mock_course_grade): else mock_course_grade.return_value for student in self.students ] - with self.assertNumQueries(11): + with self.assertNumQueries(20): all_course_grades, all_errors = self._course_grades_and_errors_for(self.course, self.students) assert {student: str(all_errors[student]) for student in all_errors} == { student3: 'Error for student3.', diff --git a/lms/djangoapps/grades/tests/test_tasks.py b/lms/djangoapps/grades/tests/test_tasks.py index 4dfd2dbef97f..347879105aad 100644 --- a/lms/djangoapps/grades/tests/test_tasks.py +++ b/lms/djangoapps/grades/tests/test_tasks.py @@ -156,8 +156,8 @@ def test_block_structure_created_only_once(self): assert mock_block_structure_create.call_count == 1 @ddt.data( - (ModuleStoreEnum.Type.split, 2, 41, True), - (ModuleStoreEnum.Type.split, 2, 41, False), + (ModuleStoreEnum.Type.split, 2, 42, True), + (ModuleStoreEnum.Type.split, 2, 42, False), ) @ddt.unpack def test_query_counts(self, default_store, num_mongo_calls, num_sql_calls, create_multiple_subsections): @@ -168,7 +168,7 @@ def test_query_counts(self, default_store, num_mongo_calls, num_sql_calls, creat self._apply_recalculate_subsection_grade() @ddt.data( - (ModuleStoreEnum.Type.split, 2, 41), + (ModuleStoreEnum.Type.split, 2, 42), ) @ddt.unpack def test_query_counts_dont_change_with_more_content(self, default_store, num_mongo_calls, num_sql_calls): @@ -255,7 +255,7 @@ def test_problem_block_with_restricted_access(self, mock_subsection_signal): UserPartition.scheme_extensions = None @ddt.data( - (ModuleStoreEnum.Type.split, 2, 41), + (ModuleStoreEnum.Type.split, 2, 42), ) @ddt.unpack def test_persistent_grades_on_course(self, default_store, num_mongo_queries, num_sql_queries): diff --git a/lms/djangoapps/grades/tests/test_transformer.py b/lms/djangoapps/grades/tests/test_transformer.py index b0cb6c0d49fb..7ef13e5b5b7c 100644 --- a/lms/djangoapps/grades/tests/test_transformer.py +++ b/lms/djangoapps/grades/tests/test_transformer.py @@ -13,6 +13,7 @@ from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import check_mongo_calls_range +import openedx.core.djangoapps.content.block_structure.api as bs_api from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.course_blocks.api import get_course_blocks from lms.djangoapps.course_blocks.transformers.tests.helpers import CourseStructureTestCase @@ -462,6 +463,7 @@ def test_modulestore_performance(self, store_type, max_mongo_calls, min_mongo_ca ) with self.store.default_store(store_type): blocks = self.build_course(course) + bs_api.update_course_in_cache(blocks['course'].id) clear_course_from_cache(blocks['course'].id) with check_mongo_calls_range(max_mongo_calls, min_mongo_calls): get_course_blocks(self.student, blocks['course'].location, self.transformers) diff --git a/lms/djangoapps/instructor/tests/test_certificates.py b/lms/djangoapps/instructor/tests/test_certificates.py index ce3433f312d8..b24ef618c7ce 100644 --- a/lms/djangoapps/instructor/tests/test_certificates.py +++ b/lms/djangoapps/instructor/tests/test_certificates.py @@ -1085,9 +1085,7 @@ def test_missing_username_and_email_error(self): res_json = json.loads(response.content.decode('utf-8')) # Assert Error Message - assert res_json['message'] == \ - 'Student username/email field is required and can not be empty.' \ - ' Kindly fill in username/email and then press "Invalidate Certificate" button.' + assert res_json['message'] == {'user': ['This field may not be blank.']} def test_invalid_user_name_error(self): """ @@ -1106,7 +1104,6 @@ def test_invalid_user_name_error(self): # Assert 400 status code in response assert response.status_code == 400 res_json = json.loads(response.content.decode('utf-8')) - # Assert Error Message assert res_json['message'] == f'{invalid_user} does not exist in the LMS. Please check your spelling and retry.' @@ -1125,7 +1122,6 @@ def test_no_generated_certificate_error(self): # Assert 400 status code in response assert response.status_code == 400 res_json = json.loads(response.content.decode('utf-8')) - # Assert Error Message assert res_json['message'] == f'The student {self.enrolled_user_2.username} does not have certificate for the course {self.course.number}. Kindly verify student username/email and the selected course are correct and try again.' # pylint: disable=line-too-long diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 2b1f28e4ceac..f27ffc32dbdb 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -107,8 +107,16 @@ from lms.djangoapps.instructor_task.data import InstructorTaskTypes from lms.djangoapps.instructor_task.models import ReportStore from lms.djangoapps.instructor.views.serializer import ( - AccessSerializer, BlockDueDateSerializer, RoleNameSerializer, ShowStudentExtensionSerializer, UserSerializer, - SendEmailSerializer, StudentAttemptsSerializer, ListInstructorTaskInputSerializer, UniqueStudentIdentifierSerializer + AccessSerializer, + BlockDueDateSerializer, + CertificateSerializer, + ListInstructorTaskInputSerializer, + RoleNameSerializer, + SendEmailSerializer, + ShowStudentExtensionSerializer, + StudentAttemptsSerializer, + UserSerializer, + UniqueStudentIdentifierSerializer ) 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 @@ -3638,12 +3646,9 @@ def build_row_errors(key, _user, row_count): return JsonResponse(results) -@transaction.non_atomic_requests -@ensure_csrf_cookie -@cache_control(no_cache=True, no_store=True, must_revalidate=True) -@require_course_permission(permissions.CERTIFICATE_INVALIDATION_VIEW) -@require_http_methods(['POST', 'DELETE']) -def certificate_invalidation_view(request, course_id): +@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True), name='dispatch') +@method_decorator(transaction.non_atomic_requests, name='dispatch') +class CertificateInvalidationView(APIView): """ Invalidate/Re-Validate students to/from certificate. @@ -3651,17 +3656,39 @@ def certificate_invalidation_view(request, course_id): :param course_id: course identifier of the course for whom to add/remove certificates exception. :return: JsonResponse object with success/error message or certificate invalidation data. """ - course_key = CourseKey.from_string(course_id) - # Validate request data and return error response in case of invalid data - try: - certificate_invalidation_data = parse_request_data(request) - student = _get_student_from_request_data(certificate_invalidation_data) - certificate = _get_certificate_for_user(course_key, student) - except ValueError as error: - return JsonResponse({'message': str(error)}, status=400) + permission_classes = (IsAuthenticated, permissions.InstructorPermission) + permission_name = permissions.CERTIFICATE_INVALIDATION_VIEW + serializer_class = CertificateSerializer + http_method_names = ['post', 'delete'] - # Invalidate certificate of the given student for the course course - if request.method == 'POST': + @method_decorator(ensure_csrf_cookie) + @method_decorator(transaction.non_atomic_requests) + def post(self, request, course_id): + """ + Invalidate/Re-Validate students to/from certificate. + """ + course_key = CourseKey.from_string(course_id) + # Validate request data and return error response in case of invalid data + serializer_data = self.serializer_class(data=request.data) + if not serializer_data.is_valid(): + # return HttpResponseBadRequest(reason=serializer_data.errors) + return JsonResponse({'message': serializer_data.errors}, status=400) + + student = serializer_data.validated_data.get('user') + notes = serializer_data.validated_data.get('notes') + + if not student: + invalid_user = request.data.get('user') + response_payload = f'{invalid_user} does not exist in the LMS. Please check your spelling and retry.' + + return JsonResponse({'message': response_payload}, status=400) + + try: + certificate = _get_certificate_for_user(course_key, student) + except Exception as ex: # pylint: disable=broad-except + return JsonResponse({'message': str(ex)}, status=400) + + # Invalidate certificate of the given student for the course course try: if certs_api.is_on_allowlist(student, course_key): log.warning(f"Invalidating certificate for student {student.id} in course {course_key} failed. " @@ -3674,15 +3701,39 @@ def certificate_invalidation_view(request, course_id): certificate_invalidation = invalidate_certificate( request, certificate, - certificate_invalidation_data, + notes, student ) + except ValueError as error: return JsonResponse({'message': str(error)}, status=400) return JsonResponse(certificate_invalidation) - # Re-Validate student certificate for the course course - elif request.method == 'DELETE': + @method_decorator(ensure_csrf_cookie) + @method_decorator(transaction.non_atomic_requests) + def delete(self, request, course_id): + """ + Invalidate/Re-Validate students to/from certificate. + """ + # Re-Validate student certificate for the course course + course_key = CourseKey.from_string(course_id) + try: + data = json.loads(self.request.body.decode('utf8') or '{}') + except Exception: # pylint: disable=broad-except + data = {} + + serializer_data = self.serializer_class(data=data) + + if not serializer_data.is_valid(): + return HttpResponseBadRequest(reason=serializer_data.errors) + + student = serializer_data.validated_data.get('user') + + try: + certificate = _get_certificate_for_user(course_key, student) + except Exception as ex: # pylint: disable=broad-except + return JsonResponse({'message': str(ex)}, status=400) + try: re_validate_certificate(request, course_key, certificate, student) except ValueError as error: @@ -3691,13 +3742,13 @@ def certificate_invalidation_view(request, course_id): return JsonResponse({}, status=204) -def invalidate_certificate(request, generated_certificate, certificate_invalidation_data, student): +def invalidate_certificate(request, generated_certificate, notes, student): """ Invalidate given GeneratedCertificate and add CertificateInvalidation record for future reference or re-validation. :param request: HttpRequest object :param generated_certificate: GeneratedCertificate object, the certificate we want to invalidate - :param certificate_invalidation_data: dict object containing data for CertificateInvalidation. + :param notes: notes values. :param student: User object, this user is tied to the generated_certificate we are going to invalidate :return: dict object containing updated certificate invalidation data. """ @@ -3720,7 +3771,7 @@ def invalidate_certificate(request, generated_certificate, certificate_invalidat certificate_invalidation = certs_api.create_certificate_invalidation_entry( generated_certificate, request.user, - certificate_invalidation_data.get("notes", ""), + notes, ) # Invalidate the certificate @@ -3731,7 +3782,7 @@ def invalidate_certificate(request, generated_certificate, certificate_invalidat 'user': student.username, 'invalidated_by': certificate_invalidation.invalidated_by.username, 'created': certificate_invalidation.created.strftime("%B %d, %Y"), - 'notes': certificate_invalidation.notes, + 'notes': notes, } diff --git a/lms/djangoapps/instructor/views/api_urls.py b/lms/djangoapps/instructor/views/api_urls.py index eef299d1d7cb..491de58ce3c5 100644 --- a/lms/djangoapps/instructor/views/api_urls.py +++ b/lms/djangoapps/instructor/views/api_urls.py @@ -89,5 +89,9 @@ name='generate_certificate_exceptions'), path('generate_bulk_certificate_exceptions', api.generate_bulk_certificate_exceptions, name='generate_bulk_certificate_exceptions'), - path('certificate_invalidation_view/', api.certificate_invalidation_view, name='certificate_invalidation_view'), + path( + 'certificate_invalidation_view/', + api.CertificateInvalidationView.as_view(), + name='certificate_invalidation_view' + ), ] diff --git a/lms/djangoapps/instructor/views/serializer.py b/lms/djangoapps/instructor/views/serializer.py index 59ac66ab838b..2ac794bc2943 100644 --- a/lms/djangoapps/instructor/views/serializer.py +++ b/lms/djangoapps/instructor/views/serializer.py @@ -228,3 +228,25 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if disable_due_datetime: self.fields['due_datetime'].required = False + + +class CertificateSerializer(serializers.Serializer): + """ + Serializer for resetting a students attempts counter or starts a task to reset all students + attempts counters. + """ + user = serializers.CharField( + help_text="Email or username of student.", required=True + ) + notes = serializers.CharField(required=False, allow_null=True, allow_blank=True) + + def validate_user(self, value): + """ + Validate that the user corresponds to an existing user. + """ + try: + user = get_student_from_identifier(value) + except User.DoesNotExist: + return None + + return user diff --git a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py index 1fb25aeb8c07..0a8c5e788bfc 100644 --- a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py +++ b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py @@ -25,6 +25,7 @@ from pytz import UTC import openedx.core.djangoapps.user_api.course_tag.api as course_tag_api +import openedx.core.djangoapps.content.block_structure.api as bs_api from xmodule.capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory # lint-amnesty, pylint: disable=wrong-import-order from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.models import CourseEnrollment, CourseEnrollmentAllowed @@ -396,6 +397,8 @@ def test_query_counts(self): ) _ = CreditCourseFactory(course_key=course.id) + bs_api.update_course_in_cache(course.id) + num_users = 5 for _ in range(num_users): user = UserFactory.create() @@ -406,7 +409,7 @@ def test_query_counts(self): with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'): with check_mongo_calls(2): - with self.assertNumQueries(54): + with self.assertNumQueries(46): CourseGradeReport.generate(None, None, course.id, {}, 'graded') def test_inactive_enrollments(self): diff --git a/lms/djangoapps/learner_dashboard/tests/test_programs.py b/lms/djangoapps/learner_dashboard/tests/test_programs.py index c1b699c44afa..0662d09a41ea 100644 --- a/lms/djangoapps/learner_dashboard/tests/test_programs.py +++ b/lms/djangoapps/learner_dashboard/tests/test_programs.py @@ -86,15 +86,9 @@ def program_sort_key(cls, program): def assert_dict_contains_subset(self, superset, subset): """ Verify that the dict superset contains the dict subset. - - Works like assertDictContainsSubset, deprecated since Python 3.2. - See: https://docs.python.org/2.7/library/unittest.html#unittest.TestCase.assertDictContainsSubset. """ - superset_keys = set(superset.keys()) - subset_keys = set(subset.keys()) - intersection = {key: superset[key] for key in superset_keys & subset_keys} - - assert subset == intersection + for key, value in subset.items(): + assert key in superset and superset[key] == value, f"{key}: {value} not found in superset or does not match" def test_login_required(self, mock_get_programs): """ diff --git a/lms/djangoapps/mobile_api/course_info/views.py b/lms/djangoapps/mobile_api/course_info/views.py index affefafe5ba0..0a173863db13 100644 --- a/lms/djangoapps/mobile_api/course_info/views.py +++ b/lms/djangoapps/mobile_api/course_info/views.py @@ -28,6 +28,7 @@ MobileCourseEnrollmentSerializer ) from openedx.core.djangoapps.content.course_overviews.models import CourseOverview +from openedx.core.djangoapps.video_pipeline.config.waffle import DEPRECATE_YOUTUBE from openedx.core.lib.api.view_utils import view_auth_classes from openedx.core.lib.xblock_utils import get_course_update_items from openedx.features.course_experience import ENABLE_COURSE_GOALS @@ -341,6 +342,7 @@ def list(self, request, **kwargs): # pylint: disable=W0221 kwargs={'api_version': api_version, 'course_id': course_id}, request=request, ), + 'deprecate_youtube': DEPRECATE_YOUTUBE.is_enabled(course_key) } course_info_context = {} diff --git a/lms/djangoapps/mobile_api/users/tests.py b/lms/djangoapps/mobile_api/users/tests.py index 6cd5e3d29a4e..2cfefaa058d9 100644 --- a/lms/djangoapps/mobile_api/users/tests.py +++ b/lms/djangoapps/mobile_api/users/tests.py @@ -9,6 +9,7 @@ import ddt import pytz +from completion.models import BlockCompletion from completion.test_utils import CompletionWaffleTestMixin, submit_completions_for_testing from django.conf import settings from django.db import transaction @@ -1381,3 +1382,145 @@ def test_discussion_tab_url(self, discussion_tab_enabled): assert isinstance(discussion_url, str) else: assert discussion_url is None + + +@ddt.ddt +class TestUserEnrollmentsStatus(MobileAPITestCase, MobileAuthUserTestMixin): + """ + Tests for /api/mobile/{api_version}/users//enrollments_status/ + """ + + REVERSE_INFO = {'name': 'user-enrollments-status', 'params': ['username', 'api_version']} + + def test_no_mobile_available_courses(self) -> None: + self.login() + courses = [CourseFactory.create(org="edx", mobile_available=False) for _ in range(3)] + for course in courses: + self.enroll(course.id) + + response = self.api_response(api_version=API_V1) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual(response.data, []) + + def test_no_enrollments(self) -> None: + self.login() + for _ in range(3): + CourseFactory.create(org="edx", mobile_available=True) + + response = self.api_response(api_version=API_V1) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual(response.data, []) + + def test_user_have_only_active_enrollments_and_no_completions(self) -> None: + self.login() + courses = [CourseFactory.create(org="edx", mobile_available=True) for _ in range(3)] + for course in courses: + self.enroll(course.id) + + response = self.api_response(api_version=API_V1) + + expected_response = [ + {'course_id': str(courses[0].course_id), 'course_name': courses[0].display_name, 'recently_active': True}, + {'course_id': str(courses[1].course_id), 'course_name': courses[1].display_name, 'recently_active': True}, + {'course_id': str(courses[2].course_id), 'course_name': courses[2].display_name, 'recently_active': True}, + ] + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual(response.data, expected_response) + + def test_user_have_active_and_inactive_enrollments_and_no_completions(self) -> None: + self.login() + courses = [CourseFactory.create(org="edx", mobile_available=True) for _ in range(3)] + for course in courses: + self.enroll(course.id) + old_course = CourseFactory.create(org="edx", mobile_available=True) + self.enroll(old_course.id) + old_enrollment = CourseEnrollment.objects.filter(user=self.user, course=old_course.course_id).first() + old_enrollment.created = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=31) + old_enrollment.save() + + response = self.api_response(api_version=API_V1) + + expected_response = [ + {'course_id': str(courses[0].course_id), 'course_name': courses[0].display_name, 'recently_active': True}, + {'course_id': str(courses[1].course_id), 'course_name': courses[1].display_name, 'recently_active': True}, + {'course_id': str(courses[2].course_id), 'course_name': courses[2].display_name, 'recently_active': True}, + {'course_id': str(old_course.course_id), 'course_name': old_course.display_name, 'recently_active': False} + ] + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual(response.data, expected_response) + + @ddt.data( + (27, True), + (28, True), + (29, True), + (31, False), + (32, False), + ) + @ddt.unpack + def test_different_enrollment_dates(self, enrolled_days_ago: int, recently_active_status: bool) -> None: + self.login() + course = CourseFactory.create(org="edx", mobile_available=True, run='1001') + self.enroll(course.id) + enrollment = CourseEnrollment.objects.filter(user=self.user, course=course.course_id).first() + enrollment.created = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=enrolled_days_ago) + enrollment.save() + + response = self.api_response(api_version=API_V1) + + expected_response = [ + { + 'course_id': str(course.course_id), + 'course_name': course.display_name, + 'recently_active': recently_active_status + } + ] + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual(response.data, expected_response) + + @ddt.data( + (27, True), + (28, True), + (29, True), + (31, False), + (32, False), + ) + @ddt.unpack + def test_different_completion_dates(self, completed_days_ago: int, recently_active_status: bool) -> None: + self.login() + course = CourseFactory.create(org="edx", mobile_available=True, run='1010') + section = BlockFactory.create( + parent=course, + category='chapter', + ) + self.enroll(course.id) + enrollment = CourseEnrollment.objects.filter(user=self.user, course=course.course_id).first() + # make enrollment older 30 days ago + enrollment.created = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=50) + enrollment.save() + completion = BlockCompletion.objects.create( + user=self.user, + context_key=course.context_key, + block_type='course', + block_key=section.location, + completion=0.5, + ) + completion.created = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=completed_days_ago) + completion.save() + + response = self.api_response(api_version=API_V1) + + expected_response = [ + { + 'course_id': str(course.course_id), + 'course_name': course.display_name, + 'recently_active': recently_active_status + } + ] + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual(response.data, expected_response) diff --git a/lms/djangoapps/mobile_api/users/urls.py b/lms/djangoapps/mobile_api/users/urls.py index 266644246e88..874730d4d0f0 100644 --- a/lms/djangoapps/mobile_api/users/urls.py +++ b/lms/djangoapps/mobile_api/users/urls.py @@ -6,7 +6,7 @@ from django.conf import settings from django.urls import re_path -from .views import UserCourseEnrollmentsList, UserCourseStatus, UserDetail +from .views import UserCourseEnrollmentsList, UserCourseStatus, UserDetail, UserEnrollmentsStatus urlpatterns = [ re_path('^' + settings.USERNAME_PATTERN + '$', UserDetail.as_view(), name='user-detail'), @@ -17,5 +17,8 @@ ), re_path(f'^{settings.USERNAME_PATTERN}/course_status_info/{settings.COURSE_ID_PATTERN}', UserCourseStatus.as_view(), - name='user-course-status') + name='user-course-status'), + re_path(f'^{settings.USERNAME_PATTERN}/enrollments_status/', + UserEnrollmentsStatus.as_view(), + name='user-enrollments-status') ] diff --git a/lms/djangoapps/mobile_api/users/views.py b/lms/djangoapps/mobile_api/users/views.py index d959e188b4ee..c86f3add9d36 100644 --- a/lms/djangoapps/mobile_api/users/views.py +++ b/lms/djangoapps/mobile_api/users/views.py @@ -3,11 +3,14 @@ """ +import datetime import logging from functools import cached_property -from typing import Optional +from typing import Dict, List, Optional, Set +import pytz from completion.exceptions import UnavailableCompletionData +from completion.models import BlockCompletion from completion.utilities import get_key_to_last_completed_block from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.contrib.auth.signals import user_logged_in @@ -17,6 +20,7 @@ from django.utils.decorators import method_decorator from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import UsageKey +from opaque_keys.edx.locator import CourseLocator from rest_framework import generics, views from rest_framework.decorators import api_view from rest_framework.permissions import SAFE_METHODS @@ -530,6 +534,133 @@ def my_user_info(request, api_version): return redirect("user-detail", api_version=api_version, username=request.user.username) +@mobile_view(is_user=True) +class UserEnrollmentsStatus(views.APIView): + """ + **Use Case** + + Get information about user's enrolments status. + + Returns active enrolment status if user was enrolled for the course + less than 30 days ago or has progressed in the course in the last 30 days. + Otherwise, the registration is considered inactive. + + USER_ENROLLMENTS_LIMIT - adds users enrollments query limit to + safe API from possible DDOS attacks. + + **Example Request** + + GET /api/mobile/{api_version}/users//enrollments_status/ + + **Response Values** + + If the request for information about the user's enrolments is successful, the + request returns an HTTP 200 "OK" response. + + The HTTP 200 response has the following values. + + * course_id (str): The course id associated with the user's enrollment. + * course_name (str): The course name associated with the user's enrollment. + * recently_active (bool): User's course enrolment status. + + + The HTTP 200 response contains a list of dictionaries that contain info + about each user's enrolment status. + + **Example Response** + + ```json + [ + { + "course_id": "course-v1:a+a+a", + "course_name": "a", + "recently_active": true + }, + { + "course_id": "course-v1:b+b+b", + "course_name": "b", + "recently_active": true + }, + { + "course_id": "course-v1:c+c+c", + "course_name": "c", + "recently_active": false + }, + ... + ] + ``` + """ + + USER_ENROLLMENTS_LIMIT = 500 + + def get(self, request, *args, **kwargs) -> Response: + """ + Gets user's enrollments status. + """ + active_status_date = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=30) + username = kwargs.get('username') + course_ids_where_user_has_completions = self._get_course_ids_where_user_has_completions( + username, + active_status_date, + ) + enrollments_status = self._build_enrollments_status_dict( + username, + active_status_date, + course_ids_where_user_has_completions + ) + return Response(enrollments_status) + + def _build_enrollments_status_dict( + self, + username: str, + active_status_date: datetime, + course_ids: Set[CourseLocator], + ) -> List[Dict[str, bool]]: + """ + Builds list with dictionaries with user's enrolments statuses. + """ + user = get_object_or_404(User, username=username) + user_enrollments = ( + CourseEnrollment + .enrollments_for_user(user) + .select_related('course') + [:self.USER_ENROLLMENTS_LIMIT] + ) + mobile_available = [ + enrollment for enrollment in user_enrollments + if is_mobile_available_for_user(user, enrollment.course_overview) + ] + enrollments_status = [] + for user_enrollment in mobile_available: + course_id = user_enrollment.course_overview.id + enrollments_status.append( + { + 'course_id': str(course_id), + 'course_name': user_enrollment.course_overview.display_name, + 'recently_active': bool( + course_id in course_ids + or user_enrollment.created > active_status_date + ) + } + ) + return enrollments_status + + @staticmethod + def _get_course_ids_where_user_has_completions( + username: str, + active_status_date: datetime, + ) -> Set[CourseLocator]: + """ + Gets course keys where user has completions. + """ + context_keys = BlockCompletion.objects.filter( + user__username=username, + created__gte=active_status_date + ).values_list('context_key', flat=True).distinct() + + return set(context_keys) + + class UserCourseEnrollmentsV4Pagination(DefaultPagination): """ Pagination for `UserCourseEnrollments` API v4. diff --git a/lms/djangoapps/support/models.py b/lms/djangoapps/support/models.py index 32df989eeddd..d8f6e2b9a1d1 100644 --- a/lms/djangoapps/support/models.py +++ b/lms/djangoapps/support/models.py @@ -21,6 +21,8 @@ class CourseResetCourseOptIn(TimeStampedModel): """ Model that represents a course which has opted in to the course reset feature. + + .. no_pii: """ course_id = CourseKeyField(max_length=255, unique=True) active = BooleanField() @@ -40,6 +42,8 @@ def all_active_course_ids(): class CourseResetAudit(TimeStampedModel): """ Model which records the course reset action's status and metadata + + .. no_pii: """ class CourseResetStatus(TextChoices): IN_PROGRESS = "in_progress" diff --git a/lms/djangoapps/user_tours/models.py b/lms/djangoapps/user_tours/models.py index 34bd7b28de27..23baacc15e1c 100644 --- a/lms/djangoapps/user_tours/models.py +++ b/lms/djangoapps/user_tours/models.py @@ -30,6 +30,8 @@ class CourseHomeChoices(models.TextChoices): class UserDiscussionsTours(models.Model): """ Model to track which discussions tours a user has seen. + + .. no_pii: """ tour_name = models.CharField(max_length=255) show_tour = models.BooleanField(default=True) diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py index 7d055ffc70ab..1c10cf7d0a83 100644 --- a/lms/djangoapps/verify_student/models.py +++ b/lms/djangoapps/verify_student/models.py @@ -1177,8 +1177,10 @@ def deadline_for_course(cls, course_key): class SSPVerificationRetryConfig(ConfigurationModel): # pylint: disable=model-missing-unicode, useless-suppression """ - SSPVerificationRetryConfig used to inject arguments - to retry_failed_photo_verifications management command + SSPVerificationRetryConfig used to inject arguments + to retry_failed_photo_verifications management command + + .. no_pii: """ class Meta: @@ -1201,6 +1203,10 @@ class VerificationAttempt(StatusModel): Plugins that implement forms of IDV can store information about IDV attempts in this model for use across the platform. + + .. pii: Contains the name of the user + .. pii_types: name + .. pii_retirement: local_api """ user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE) name = models.CharField(blank=True, max_length=255) diff --git a/lms/envs/common.py b/lms/envs/common.py index c7e38441e35b..d74c28e75687 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -3821,6 +3821,16 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring # By default, don't use a file prefix ORA2_FILE_PREFIX = None +# .. setting_name: ORA_PEER_LEASE_EXPIRATION_HOURS +# .. setting_default: 8 +# .. setting_description: Amount of time before a lease on a peer submission expires +ORA_PEER_LEASE_EXPIRATION_HOURS = 8 + +# .. setting_name: ORA_STAFF_LEASE_EXPIRATION_HOURS +# .. setting_default: 8 +# .. setting_description: Amount of time before a lease on a staff submission expires +ORA_STAFF_LEASE_EXPIRATION_HOURS = 8 + # Default File Upload Storage bucket and prefix. Used by the FileUpload Service. FILE_UPLOAD_STORAGE_BUCKET_NAME = 'SET-ME-PLEASE (ex. bucket-name)' FILE_UPLOAD_STORAGE_PREFIX = 'submissions_attachments' diff --git a/openedx/core/djangoapps/agreements/models.py b/openedx/core/djangoapps/agreements/models.py index 461d7936c4bb..2672a4f47b24 100644 --- a/openedx/core/djangoapps/agreements/models.py +++ b/openedx/core/djangoapps/agreements/models.py @@ -27,6 +27,8 @@ class Meta: class LTIPIITool(TimeStampedModel): """ This model stores the relationship between a course and the LTI tools in the course that share PII. + + .. no_pii: """ course_key = CourseKeyField(max_length=255, unique=True, db_index=True) lti_tools = models.JSONField() @@ -39,6 +41,8 @@ class Meta: class LTIPIISignature(TimeStampedModel): """ This model stores a user's acknowledgement to share PII via LTI tools in a particular course. + + .. no_pii: """ user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE) course_key = CourseKeyField(max_length=255, db_index=True) @@ -57,6 +61,8 @@ class Meta: class ProctoringPIISignature(TimeStampedModel): """ This model stores a user's acknowledgment to share PII via proctoring in a particular course. + + .. no_pii: """ user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE) course_key = CourseKeyField(max_length=255, db_index=True) diff --git a/openedx/core/djangoapps/auth_exchange/tests/test_views.py b/openedx/core/djangoapps/auth_exchange/tests/test_views.py index 9d8f21e6eed5..0ab08ffe37d8 100644 --- a/openedx/core/djangoapps/auth_exchange/tests/test_views.py +++ b/openedx/core/djangoapps/auth_exchange/tests/test_views.py @@ -168,11 +168,15 @@ def _verify_response(self, access_token, expected_status_code, token_type='Beare if expected_cookie_name: assert expected_cookie_name in response.cookies - def _create_dot_access_token(self, grant_type='Client credentials'): + def _create_dot_access_token(self, grant_type='Client credentials', skip_authorization=False): """ Create dot based access token """ - dot_application = dot_factories.ApplicationFactory(user=self.user, authorization_grant_type=grant_type) + dot_application = dot_factories.ApplicationFactory( + user=self.user, + authorization_grant_type=grant_type, + skip_authorization=skip_authorization, + ) return dot_factories.AccessTokenFactory(user=self.user, application=dot_application) def test_failure_with_invalid_token(self): @@ -189,6 +193,10 @@ def test_failure_with_dot_client_credentials_unsupported(self): access_token = self._create_dot_access_token() self._verify_response(access_token, expected_status_code=401) + def test_dot_client_credentials_supported_if_authorization_skipped(self): + access_token = self._create_dot_access_token(skip_authorization=True) + self._verify_response(access_token, expected_status_code=204, expected_cookie_name='sessionid') + def _create_jwt_token(self, grant_type='password', scope='email profile', use_asymmetric_key=True): """ Create jwt token diff --git a/openedx/core/djangoapps/auth_exchange/views.py b/openedx/core/djangoapps/auth_exchange/views.py index e4b302595277..60470aeba520 100644 --- a/openedx/core/djangoapps/auth_exchange/views.py +++ b/openedx/core/djangoapps/auth_exchange/views.py @@ -132,9 +132,10 @@ def _get_path_of_arbitrary_backend_for_user(user): return backend_path @staticmethod - def _ensure_access_token_has_password_grant(request): + def _ensure_access_token_has_password_grant_or_privileged_application(request): """ - Ensures the access token provided has password type grant. + Ensures the access token provided has password type grant, or if 'skip_authorization' + has been enabled, implying this is a trusted application. """ if is_jwt_authenticated(request): jwt_payload = get_decoded_jwt_from_auth(request) @@ -143,12 +144,17 @@ def _ensure_access_token_has_password_grant(request): else: token_query = dot_models.AccessToken.objects.select_related('user') dot_token = token_query.filter(token=request.auth).first() - if dot_token and dot_token.application.authorization_grant_type == dot_models.Application.GRANT_PASSWORD: + if dot_token and ( + dot_token.application.authorization_grant_type == dot_models.Application.GRANT_PASSWORD + or dot_token.application.skip_authorization + ): return raise AuthenticationFailed({ 'error_code': 'non_supported_token', - 'developer_message': 'Only access tokens with grant type password are supported.' + 'developer_message': 'Only Django Oauth Toolkit access tokens for applications which ' + 'are trusted (with "skip_authentication" set to True, or with grant type ' + 'password) are supported.' }) @staticmethod @@ -194,7 +200,7 @@ def post(self, request): request.user.backend = self._get_path_of_arbitrary_backend_for_user(request.user) self._ensure_user_is_not_disabled(request) - self._ensure_access_token_has_password_grant(request) + self._ensure_access_token_has_password_grant_or_privileged_application(request) self._ensure_jwt_is_asymmetric(request) login(request, request.user) # login generates and stores the user's cookies in the session diff --git a/openedx/core/djangoapps/content/block_structure/__init__.py b/openedx/core/djangoapps/content/block_structure/__init__.py index f77cf27f041f..00ed1b12d2ef 100644 --- a/openedx/core/djangoapps/content/block_structure/__init__.py +++ b/openedx/core/djangoapps/content/block_structure/__init__.py @@ -59,4 +59,10 @@ Note: A partial subset (as an ordered list) of the registered transformers can be requested during the Transform phase, allowing the client to manipulate exactly which transformers to call. + +Links to Other Block Structure Related Documentation: + + * https://openedx.atlassian.net/wiki/spaces/AC/pages/154861855/Block+Structure+Cache+Invalidation+Proposal + * https://openedx.atlassian.net/wiki/spaces/AC/pages/34734111/Course+Block+Transformers + * https://openedx.atlassian.net/wiki/spaces/AC/pages/41910826/Course+Blocks+API+Storage+Cache+Requirements """ diff --git a/openedx/core/djangoapps/content/block_structure/config/__init__.py b/openedx/core/djangoapps/content/block_structure/config/__init__.py index df82bfb068e2..79837df86be6 100644 --- a/openedx/core/djangoapps/content/block_structure/config/__init__.py +++ b/openedx/core/djangoapps/content/block_structure/config/__init__.py @@ -9,37 +9,6 @@ from .models import BlockStructureConfiguration -# Switches -# .. toggle_name: block_structure.storage_backing_for_cache -# .. toggle_implementation: WaffleSwitch -# .. toggle_default: False -# .. toggle_description: When enabled, block structures are stored in a more permanent storage, -# like a database, which provides an additional backup for cache misses, instead having them -# regenerated. The regenration of block structures is a time consuming process. Therefore, -# enabling this switch is recommended for Production. -# .. toggle_warning: Depends on `BLOCK_STRUCTURES_SETTINGS['STORAGE_CLASS']` and -# `BLOCK_STRUCTURES_SETTINGS['STORAGE_KWARGS']`. -# This switch will likely be deprecated and removed. -# The annotation will be updated with the DEPR ticket once that process has started. -# .. toggle_use_cases: temporary -# .. toggle_creation_date: 2017-02-23 -# .. toggle_target_removal_date: 2017-05-23 -# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/14512, -# https://github.com/openedx/edx-platform/pull/14770, -# https://openedx.atlassian.net/browse/DEPR-145 -STORAGE_BACKING_FOR_CACHE = WaffleSwitch( - "block_structure.storage_backing_for_cache", __name__ -) - - -def enable_storage_backing_for_cache_in_request(): - """ - Manually override the value of the STORAGE_BACKING_FOR_CACHE switch in the context of the request. - This function should not be replicated, as it accesses a protected member, and it shouldn't. - """ - # pylint: disable=protected-access - STORAGE_BACKING_FOR_CACHE._cached_switches[STORAGE_BACKING_FOR_CACHE.name] = True - @request_cached() def num_versions_to_keep(): diff --git a/openedx/core/djangoapps/content/block_structure/management/commands/generate_course_blocks.py b/openedx/core/djangoapps/content/block_structure/management/commands/generate_course_blocks.py index 4da85f5c52bb..047c945fe406 100644 --- a/openedx/core/djangoapps/content/block_structure/management/commands/generate_course_blocks.py +++ b/openedx/core/djangoapps/content/block_structure/management/commands/generate_course_blocks.py @@ -10,7 +10,6 @@ import openedx.core.djangoapps.content.block_structure.api as api import openedx.core.djangoapps.content.block_structure.store as store import openedx.core.djangoapps.content.block_structure.tasks as tasks -from openedx.core.djangoapps.content.block_structure.config import enable_storage_backing_for_cache_in_request from openedx.core.lib.command_utils import ( get_mutually_exclusive_required_option, parse_course_keys, @@ -75,12 +74,6 @@ def add_arguments(self, parser): default=0, type=int, ) - parser.add_argument( - '--with_storage', - help='Store the course blocks in Storage, overriding value of the storage_backing_for_cache waffle switch', - action='store_true', - default=False, - ) def handle(self, *args, **options): @@ -129,9 +122,6 @@ def _generate_course_blocks(self, options, course_keys): """ Generates course blocks for the given course_keys per the given options. """ - if options.get('with_storage'): - enable_storage_backing_for_cache_in_request() - for course_key in course_keys: try: self._generate_for_course(options, course_key) @@ -150,7 +140,7 @@ def _generate_for_course(self, options, course_key): action = tasks.update_course_in_cache_v2 if options.get('force_update') else tasks.get_course_in_cache_v2 task_options = {'routing_key': options['routing_key']} if options.get('routing_key') else {} result = action.apply_async( - kwargs=dict(course_id=str(course_key), with_storage=options.get('with_storage')), + kwargs=dict(course_id=str(course_key)), **task_options ) log.info('BlockStructure: ENQUEUED generating for course: %s, task_id: %s.', course_key, result.id) diff --git a/openedx/core/djangoapps/content/block_structure/management/commands/tests/test_generate_course_blocks.py b/openedx/core/djangoapps/content/block_structure/management/commands/tests/test_generate_course_blocks.py index 5b08c5e95df7..430f374b07cc 100644 --- a/openedx/core/djangoapps/content/block_structure/management/commands/tests/test_generate_course_blocks.py +++ b/openedx/core/djangoapps/content/block_structure/management/commands/tests/test_generate_course_blocks.py @@ -85,15 +85,8 @@ def test_all_courses(self, force_update): assert mock_update_from_store.call_count == (self.num_courses if force_update else 0) def test_one_course(self): - self._assert_courses_not_in_block_cache(*self.course_keys) self.command.handle(courses=[str(self.course_keys[0])]) self._assert_courses_in_block_cache(self.course_keys[0]) - self._assert_courses_not_in_block_cache(*self.course_keys[1:]) - self._assert_courses_not_in_block_storage(*self.course_keys) - - def test_with_storage(self): - self.command.handle(with_storage=True, courses=[str(self.course_keys[0])]) - self._assert_courses_in_block_cache(self.course_keys[0]) self._assert_courses_in_block_storage(self.course_keys[0]) self._assert_courses_not_in_block_storage(*self.course_keys[1:]) diff --git a/openedx/core/djangoapps/content/block_structure/manager.py b/openedx/core/djangoapps/content/block_structure/manager.py index 2f8a041c3163..49f423ce7ac3 100644 --- a/openedx/core/djangoapps/content/block_structure/manager.py +++ b/openedx/core/djangoapps/content/block_structure/manager.py @@ -35,7 +35,7 @@ def __init__(self, root_block_usage_key, modulestore, cache): self.modulestore = modulestore self.store = BlockStructureStore(cache) - def get_transformed(self, transformers, starting_block_usage_key=None, collected_block_structure=None): + def get_transformed(self, transformers, starting_block_usage_key=None, collected_block_structure=None, user=None): """ Returns the transformed Block Structure for the root_block_usage_key, starting at starting_block_usage_key, getting block data from the cache @@ -57,11 +57,14 @@ def get_transformed(self, transformers, starting_block_usage_key=None, collected get_collected. Can be optionally provided if already available, for optimization. + user (django.contrib.auth.models.User) - User object for + which the block structure is to be transformed. + Returns: BlockStructureBlockData - A transformed block structure, starting at starting_block_usage_key. """ - block_structure = collected_block_structure.copy() if collected_block_structure else self.get_collected() + block_structure = collected_block_structure.copy() if collected_block_structure else self.get_collected(user) if starting_block_usage_key: # Override the root_block_usage_key so traversals start at the @@ -77,7 +80,7 @@ def get_transformed(self, transformers, starting_block_usage_key=None, collected transformers.transform(block_structure) return block_structure - def get_collected(self): + def get_collected(self, user=None): """ Returns the collected Block Structure for the root_block_usage_key, getting block data from the cache and modulestore, as needed. @@ -86,6 +89,10 @@ def get_collected(self): the modulestore is accessed if needed (at cache miss), and the transformers data is collected if needed. + In the case of a cache miss, the function bypasses runtime access checks for the current + user. This is done to prevent inconsistencies in the data, which can occur when + certain blocks are inaccessible due to access restrictions. + Returns: BlockStructureBlockData - A collected block structure, starting at root_block_usage_key, with collected data @@ -99,7 +106,15 @@ def get_collected(self): BlockStructureTransformers.verify_versions(block_structure) except (BlockStructureNotFound, TransformerDataIncompatible): - block_structure = self._update_collected() + if user and getattr(user, "known", True): + # This bypasses the runtime access checks. When we are populating the course blocks cache, + # we do not want to perform access checks. Access checks result in inconsistent blocks where + # inaccessible blocks are missing from the cache. Cached course blocks are then used for all the users. + user.known = False + block_structure = self._update_collected() + user.known = True + else: + block_structure = self._update_collected() return block_structure diff --git a/openedx/core/djangoapps/content/block_structure/models.py b/openedx/core/djangoapps/content/block_structure/models.py index 4872e09346d9..c2837e1a77f0 100644 --- a/openedx/core/djangoapps/content/block_structure/models.py +++ b/openedx/core/djangoapps/content/block_structure/models.py @@ -74,7 +74,6 @@ def _bs_model_storage(): # .. setting_default: None # .. setting_description: Specifies the storage used for storage-backed block structure cache. # For more information, check https://github.com/openedx/edx-platform/pull/14571. - # .. setting_warnings: Depends on `block_structure.storage_backing_for_cache`. storage_class = settings.BLOCK_STRUCTURES_SETTINGS.get('STORAGE_CLASS') # .. setting_name: BLOCK_STRUCTURES_SETTINGS['STORAGE_KWARGS'] @@ -82,8 +81,7 @@ def _bs_model_storage(): # .. setting_description: Specifies the keyword arguments needed to setup the storage, which # would be used for storage-backed block structure cache. # For more information, check https://github.com/openedx/edx-platform/pull/14571. - # .. setting_warnings: Depends on `BLOCK_STRUCTURES_SETTINGS['STORAGE_CLASS']` and on - # `block_structure.storage_backing_for_cache`. + # .. setting_warnings: Depends on `BLOCK_STRUCTURES_SETTINGS['STORAGE_CLASS']` storage_kwargs = settings.BLOCK_STRUCTURES_SETTINGS.get('STORAGE_KWARGS', {}) return get_storage(storage_class, **storage_kwargs) diff --git a/openedx/core/djangoapps/content/block_structure/store.py b/openedx/core/djangoapps/content/block_structure/store.py index 2342079bdc6e..37a2b57449c0 100644 --- a/openedx/core/djangoapps/content/block_structure/store.py +++ b/openedx/core/djangoapps/content/block_structure/store.py @@ -19,26 +19,6 @@ logger = getLogger(__name__) # pylint: disable=C0103 -class StubModel: - """ - Stub model to use when storage backing is disabled. - By using this stub, we eliminate the need for extra - conditional statements in the code. - """ - - def __init__(self, root_block_usage_key): - self.data_usage_key = root_block_usage_key - - def __str__(self): - return str(self.data_usage_key) - - def delete(self): - """ - Noop delete method. - """ - pass # lint-amnesty, pylint: disable=unnecessary-pass - - class BlockStructureStore: """ Storage for BlockStructure objects. @@ -120,13 +100,12 @@ def is_up_to_date(self, root_block_usage_key, modulestore): Returns whether the data in storage for the given key is already up-to-date with the version in the given modulestore. """ - if config.STORAGE_BACKING_FOR_CACHE.is_enabled(): - try: - bs_model = self._get_model(root_block_usage_key) - root_block = modulestore.get_item(root_block_usage_key) - return self._version_data_of_model(bs_model) == self._version_data_of_block(root_block) - except BlockStructureNotFound: - pass + try: + bs_model = self._get_model(root_block_usage_key) + root_block = modulestore.get_item(root_block_usage_key) + return self._version_data_of_model(bs_model) == self._version_data_of_block(root_block) + except BlockStructureNotFound: + pass return False @@ -134,26 +113,20 @@ def _get_model(self, root_block_usage_key): """ Returns the model associated with the given key. """ - if config.STORAGE_BACKING_FOR_CACHE.is_enabled(): - return BlockStructureModel.get(root_block_usage_key) - else: - return StubModel(root_block_usage_key) + return BlockStructureModel.get(root_block_usage_key) def _update_or_create_model(self, block_structure, serialized_data): """ Updates or creates the model for the given block_structure and serialized_data. """ - if config.STORAGE_BACKING_FOR_CACHE.is_enabled(): - root_block = block_structure[block_structure.root_block_usage_key] - bs_model, _ = BlockStructureModel.update_or_create( - serialized_data, - data_usage_key=block_structure.root_block_usage_key, - **self._version_data_of_block(root_block) - ) - return bs_model - else: - return StubModel(block_structure.root_block_usage_key) + root_block = block_structure[block_structure.root_block_usage_key] + bs_model, _ = BlockStructureModel.update_or_create( + serialized_data, + data_usage_key=block_structure.root_block_usage_key, + **self._version_data_of_block(root_block) + ) + return bs_model def _add_to_cache(self, serialized_data, bs_model): """ @@ -186,9 +159,6 @@ def _get_from_store(self, bs_model): Raises: BlockStructureNotFound if not found. """ - if not config.STORAGE_BACKING_FOR_CACHE.is_enabled(): - raise BlockStructureNotFound(bs_model.data_usage_key) - return bs_model.get_serialized_data() def _serialize(self, block_structure): @@ -226,14 +196,9 @@ def _deserialize(self, serialized_data, root_block_usage_key): def _encode_root_cache_key(bs_model): """ Returns the cache key to use for the given - BlockStructureModel or StubModel. + BlockStructureModel. """ - if config.STORAGE_BACKING_FOR_CACHE.is_enabled(): - return str(bs_model) - return "v{version}.root.key.{root_usage_key}".format( - version=str(BlockStructureBlockData.VERSION), - root_usage_key=str(bs_model.data_usage_key), - ) + return str(bs_model) @staticmethod def _version_data_of_block(root_block): diff --git a/openedx/core/djangoapps/content/block_structure/tasks.py b/openedx/core/djangoapps/content/block_structure/tasks.py index 7c3c2805fb10..5796b8167b2b 100644 --- a/openedx/core/djangoapps/content/block_structure/tasks.py +++ b/openedx/core/djangoapps/content/block_structure/tasks.py @@ -14,7 +14,6 @@ from xmodule.capa.responsetypes import LoncapaProblemError from openedx.core.djangoapps.content.block_structure import api -from openedx.core.djangoapps.content.block_structure.config import enable_storage_backing_for_cache_in_request from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order log = logging.getLogger('edx.celery.task') @@ -43,8 +42,6 @@ def update_course_in_cache_v2(self, **kwargs): Updates the course blocks (mongo -> BlockStructure) for the specified course. Keyword Arguments: course_id (string) - The string serialized value of the course key. - with_storage (boolean) - Whether or not storage backing should be - enabled for the generated block structure(s). """ _update_course_in_cache(self, **kwargs) @@ -62,8 +59,6 @@ def _update_course_in_cache(self, **kwargs): """ Updates the course blocks (mongo -> BlockStructure) for the specified course. """ - if kwargs.get('with_storage'): - enable_storage_backing_for_cache_in_request() _call_and_retry_if_needed(self, api.update_course_in_cache, **kwargs) @@ -74,8 +69,6 @@ def get_course_in_cache_v2(self, **kwargs): Gets the course blocks for the specified course, updating the cache if needed. Keyword Arguments: course_id (string) - The string serialized value of the course key. - with_storage (boolean) - Whether or not storage backing should be - enabled for any generated block structure(s). """ _get_course_in_cache(self, **kwargs) @@ -93,8 +86,6 @@ def _get_course_in_cache(self, **kwargs): """ Gets the course blocks for the specified course, updating the cache if needed. """ - if kwargs.get('with_storage'): - enable_storage_backing_for_cache_in_request() _call_and_retry_if_needed(self, api.get_course_in_cache, **kwargs) diff --git a/openedx/core/djangoapps/content/block_structure/tests/helpers.py b/openedx/core/djangoapps/content/block_structure/tests/helpers.py index 93655c79e16e..f0a20554e6d2 100644 --- a/openedx/core/djangoapps/content/block_structure/tests/helpers.py +++ b/openedx/core/djangoapps/content/block_structure/tests/helpers.py @@ -274,10 +274,14 @@ def create_block_structure(self, children_map, block_structure_cls=BlockStructur # create empty block structure block_structure = block_structure_cls(root_block_usage_key=self.block_key_factory(0)) - # _add_relation + # _add_relation and blocks for parent, children in enumerate(children_map): + if isinstance(block_structure, BlockStructureBlockData): + block_structure._get_or_create_block(self.block_key_factory(parent)) # pylint: disable=protected-access for child in children: block_structure._add_relation(self.block_key_factory(parent), self.block_key_factory(child)) # pylint: disable=protected-access + if isinstance(block_structure, BlockStructureBlockData): + block_structure._get_or_create_block(self.block_key_factory(child)) # pylint: disable=protected-access return block_structure def get_parents_map(self, children_map): diff --git a/openedx/core/djangoapps/content/block_structure/tests/test_factory.py b/openedx/core/djangoapps/content/block_structure/tests/test_factory.py index b9fb8c5e1077..00efa393d8fa 100644 --- a/openedx/core/djangoapps/content/block_structure/tests/test_factory.py +++ b/openedx/core/djangoapps/content/block_structure/tests/test_factory.py @@ -5,6 +5,7 @@ import pytest from django.test import TestCase +from opaque_keys.edx.keys import CourseKey from xmodule.modulestore.exceptions import ItemNotFoundError from ..exceptions import BlockStructureNotFound @@ -18,14 +19,22 @@ class TestBlockStructureFactory(TestCase, ChildrenMapTestMixin): Tests for BlockStructureFactory """ + def block_key_factory(self, block_id): + """ + Returns a usage_key object for the given block_id. + This overrides the method in the ChildrenMapTestMixin. + """ + return CourseKey.from_string("course-v1:org+course+run").make_usage_key("html", str(block_id)) + def setUp(self): super().setUp() self.children_map = self.SIMPLE_CHILDREN_MAP self.modulestore = MockModulestoreFactory.create(self.children_map, self.block_key_factory) def test_from_modulestore(self): + usage_key = CourseKey.from_string("course-v1:org+course+run").make_usage_key("html", "0") block_structure = BlockStructureFactory.create_from_modulestore( - root_block_usage_key=0, modulestore=self.modulestore + root_block_usage_key=usage_key, modulestore=self.modulestore ) self.assert_block_structure(block_structure, self.children_map) @@ -48,15 +57,18 @@ def test_from_cache(self): def test_from_cache_none(self): store = BlockStructureStore(MockCache()) + # Non-existent usage key + usage_key = CourseKey.from_string("course-v1:org+course+run").make_usage_key("html", "0") with pytest.raises(BlockStructureNotFound): BlockStructureFactory.create_from_store( - root_block_usage_key=0, + root_block_usage_key=usage_key, block_structure_store=store, ) def test_new(self): + usage_key = CourseKey.from_string("course-v1:org+course+run").make_usage_key("html", "0") block_structure = BlockStructureFactory.create_from_modulestore( - root_block_usage_key=0, modulestore=self.modulestore + root_block_usage_key=usage_key, modulestore=self.modulestore ) new_structure = BlockStructureFactory.create_new( block_structure.root_block_usage_key, diff --git a/openedx/core/djangoapps/content/block_structure/tests/test_manager.py b/openedx/core/djangoapps/content/block_structure/tests/test_manager.py index 8e5b585ca879..493e4c34714f 100644 --- a/openedx/core/djangoapps/content/block_structure/tests/test_manager.py +++ b/openedx/core/djangoapps/content/block_structure/tests/test_manager.py @@ -5,10 +5,8 @@ import pytest import ddt from django.test import TestCase -from edx_toggles.toggles.testutils import override_waffle_switch from ..block_structure import BlockStructureBlockData -from ..config import STORAGE_BACKING_FOR_CACHE from ..exceptions import UsageKeyNotInBlockStructure from ..manager import BlockStructureManager from ..transformers import BlockStructureTransformers @@ -177,20 +175,18 @@ def test_get_collected_cached(self): self.collect_and_verify(expect_modulestore_called=False, expect_cache_updated=False) assert TestTransformer1.collect_call_count == 1 - @ddt.data(True, False) - def test_update_collected_if_needed(self, with_storage_backing): - with override_waffle_switch(STORAGE_BACKING_FOR_CACHE, active=with_storage_backing): - with mock_registered_transformers(self.registered_transformers): - assert TestTransformer1.collect_call_count == 0 + def test_update_collected_if_needed(self): + with mock_registered_transformers(self.registered_transformers): + assert TestTransformer1.collect_call_count == 0 - self.bs_manager.update_collected_if_needed() - assert TestTransformer1.collect_call_count == 1 + self.bs_manager.update_collected_if_needed() + assert TestTransformer1.collect_call_count == 1 - self.bs_manager.update_collected_if_needed() - expected_count = 1 if with_storage_backing else 2 - assert TestTransformer1.collect_call_count == expected_count + self.bs_manager.update_collected_if_needed() + expected_count = 1 + assert TestTransformer1.collect_call_count == expected_count - self.collect_and_verify(expect_modulestore_called=False, expect_cache_updated=False) + self.collect_and_verify(expect_modulestore_called=False, expect_cache_updated=False) def test_get_collected_transformer_version(self): self.collect_and_verify(expect_modulestore_called=True, expect_cache_updated=True) @@ -212,8 +208,8 @@ def test_get_collected_transformer_version(self): def test_get_collected_structure_version(self): self.collect_and_verify(expect_modulestore_called=True, expect_cache_updated=True) BlockStructureBlockData.VERSION += 1 - self.collect_and_verify(expect_modulestore_called=True, expect_cache_updated=True) - assert TestTransformer1.collect_call_count == 2 + self.collect_and_verify(expect_modulestore_called=False, expect_cache_updated=False) + assert TestTransformer1.collect_call_count == 1 def test_clear(self): self.collect_and_verify(expect_modulestore_called=True, expect_cache_updated=True) diff --git a/openedx/core/djangoapps/content/block_structure/tests/test_store.py b/openedx/core/djangoapps/content/block_structure/tests/test_store.py index 8d6fc8017d0f..63a02efa7af8 100644 --- a/openedx/core/djangoapps/content/block_structure/tests/test_store.py +++ b/openedx/core/djangoapps/content/block_structure/tests/test_store.py @@ -4,11 +4,9 @@ import pytest import ddt -from edx_toggles.toggles.testutils import override_waffle_switch from openedx.core.djangolib.testing.utils import CacheIsolationTestCase -from ..config import STORAGE_BACKING_FOR_CACHE from ..config.models import BlockStructureConfiguration from ..exceptions import BlockStructureNotFound from ..store import BlockStructureStore @@ -46,40 +44,27 @@ def add_transformers(self): value=f'{transformer.name()} val', ) - @ddt.data(True, False) - def test_get_none(self, with_storage_backing): - with override_waffle_switch(STORAGE_BACKING_FOR_CACHE, active=with_storage_backing): - with pytest.raises(BlockStructureNotFound): - self.store.get(self.block_structure.root_block_usage_key) - - @ddt.data(True, False) - def test_add_and_get(self, with_storage_backing): - with override_waffle_switch(STORAGE_BACKING_FOR_CACHE, active=with_storage_backing): - self.store.add(self.block_structure) - stored_value = self.store.get(self.block_structure.root_block_usage_key) - assert stored_value is not None - self.assert_block_structure(stored_value, self.children_map) + def test_get_none(self): + with pytest.raises(BlockStructureNotFound): + self.store.get(self.block_structure.root_block_usage_key) - @ddt.data(True, False) - def test_delete(self, with_storage_backing): - with override_waffle_switch(STORAGE_BACKING_FOR_CACHE, active=with_storage_backing): - self.store.add(self.block_structure) - self.store.delete(self.block_structure.root_block_usage_key) - with pytest.raises(BlockStructureNotFound): - self.store.get(self.block_structure.root_block_usage_key) + def test_add_and_get(self): + self.store.add(self.block_structure) + stored_value = self.store.get(self.block_structure.root_block_usage_key) + assert stored_value is not None + self.assert_block_structure(stored_value, self.children_map) - def test_uncached_without_storage(self): + def test_delete(self): self.store.add(self.block_structure) - self.mock_cache.map.clear() + self.store.delete(self.block_structure.root_block_usage_key) with pytest.raises(BlockStructureNotFound): self.store.get(self.block_structure.root_block_usage_key) def test_uncached_with_storage(self): - with override_waffle_switch(STORAGE_BACKING_FOR_CACHE, active=True): - self.store.add(self.block_structure) - self.mock_cache.map.clear() - stored_value = self.store.get(self.block_structure.root_block_usage_key) - self.assert_block_structure(stored_value, self.children_map) + self.store.add(self.block_structure) + self.mock_cache.map.clear() + stored_value = self.store.get(self.block_structure.root_block_usage_key) + self.assert_block_structure(stored_value, self.children_map) @ddt.data(1, 5, None) def test_cache_timeout(self, timeout): diff --git a/openedx/core/djangoapps/content/learning_sequences/models.py b/openedx/core/djangoapps/content/learning_sequences/models.py index de0881abe5f6..5d2ee34bbc9f 100644 --- a/openedx/core/djangoapps/content/learning_sequences/models.py +++ b/openedx/core/djangoapps/content/learning_sequences/models.py @@ -53,6 +53,8 @@ class LearningContext(TimeStampedModel): because this table can contain things that are not courses. It is okay to make a foreign key against this table. + + .. no_pii: """ id = models.BigAutoField(primary_key=True) context_key = LearningContextKeyField( @@ -74,6 +76,8 @@ def __str__(self): class CourseContext(TimeStampedModel): """ A model containing course specific information e.g course_visibility + + .. no_pii: """ learning_context = models.OneToOneField( LearningContext, on_delete=models.CASCADE, primary_key=True, related_name="course_context" @@ -106,6 +110,8 @@ class LearningSequence(TimeStampedModel): CourseSectionSequence. It is okay to make a foreign key against this table. + + .. no_pii: """ id = models.BigAutoField(primary_key=True) learning_context = models.ForeignKey( @@ -131,6 +137,8 @@ class CourseContentVisibilityMixin(models.Model): We keep the XBlock field names here, even if they're somewhat misleading. Please read the comments carefully for each field. + + .. no_pii: """ # This is an obscure, OLX-only flag (there is no UI for it in Studio) that # lets you define a Sequence that is reachable by direct URL but not shown @@ -174,6 +182,8 @@ class UserPartitionGroup(models.Model): UserPartitionGroups are not associated with LearningSequence directly because User Partitions often carry course-level assumptions (e.g. Enrollment Track) that don't make sense outside of a Course. + + .. no_pii: """ id = models.BigAutoField(primary_key=True) partition_id = models.BigIntegerField(null=False) @@ -191,6 +201,8 @@ class Meta: class CourseSection(CourseContentVisibilityMixin, TimeStampedModel): """ Course Section data, mapping to the 'chapter' block type. + + .. no_pii: """ id = models.BigAutoField(primary_key=True) course_context = models.ForeignKey( @@ -225,6 +237,8 @@ class SectionPartitionGroup(models.Model): Used for the user_partition_groups ManyToManyField field in the CourseSection model above. Adds a cascading delete which will delete these many-to-many relations whenever a UserPartitionGroup or CourseSection object is deleted. + + .. no_pii: """ class Meta: unique_together = [ @@ -249,6 +263,8 @@ class CourseSectionSequence(CourseContentVisibilityMixin, TimeStampedModel): Do NOT make a foreign key against this table, as the values are deleted and re-created on course publish. + + .. no_pii: """ id = models.BigAutoField(primary_key=True) course_context = models.ForeignKey( @@ -289,6 +305,8 @@ class SectionSequencePartitionGroup(models.Model): Used for the user_partition_groups ManyToManyField field in the CourseSectionSequence model above. Adds a cascading delete which will delete these many-to-many relations whenever a UserPartitionGroup or CourseSectionSequence object is deleted. + + .. no_pii: """ class Meta: unique_together = [ @@ -303,6 +321,8 @@ class CourseSequenceExam(TimeStampedModel): """ This model stores XBlock information that affects outline level information pertaining to special exams + + .. no_pii: """ course_section_sequence = models.OneToOneField(CourseSectionSequence, on_delete=models.CASCADE, related_name='exam') @@ -318,6 +338,8 @@ class PublishReport(models.Model): All these fields could be derived with aggregate SQL functions, but it would be slower and make the admin code more complex. Since we only write at publish time, keeping things in sync is less of a concern. + + .. no_pii: """ learning_context = models.OneToOneField( LearningContext, on_delete=models.CASCADE, related_name='publish_report' @@ -350,6 +372,8 @@ class ContentError(models.Model): freeform messages. It is quite possible that at some point we will come up with a more comprehensive taxonomy of error messages, at which point we could do a backfill to regenerate this data in a more normalized way. + + .. no_pii: """ id = models.BigAutoField(primary_key=True) publish_report = models.ForeignKey( diff --git a/openedx/core/djangoapps/content/search/documents.py b/openedx/core/djangoapps/content/search/documents.py index fb385ccc4503..0dd02683ceea 100644 --- a/openedx/core/djangoapps/content/search/documents.py +++ b/openedx/core/djangoapps/content/search/documents.py @@ -80,6 +80,7 @@ class Fields: published = "published" published_display_name = "display_name" published_description = "description" + published_num_children = "num_children" # Note: new fields or values can be added at any time, but if they need to be indexed for filtering or keyword # search, the index configuration will need to be changed, which is only done as part of the 'reindex_studio' @@ -106,9 +107,12 @@ def meili_id_from_opaque_key(usage_key: UsageKey) -> str: we could use PublishableEntity's primary key / UUID instead. """ # The slugified key _may_ not be unique so we append a hashed string to make it unique: - key_bin = str(usage_key).encode() - suffix = blake2b(key_bin, digest_size=4).hexdigest() # When we use Python 3.9+, should add usedforsecurity=False - return slugify(str(usage_key)) + "-" + suffix + key_str = str(usage_key) + key_bin = key_str.encode() + + suffix = blake2b(key_bin, digest_size=4, usedforsecurity=False).hexdigest() + + return f"{slugify(key_str)}-{suffix}" def _meili_access_id_from_context_key(context_key: LearningContextKey) -> int: @@ -485,6 +489,15 @@ def searchable_doc_for_collection( if collection: assert collection.key == collection_key + draft_num_children = authoring_api.filter_publishable_entities( + collection.entities, + has_draft=True, + ).count() + published_num_children = authoring_api.filter_publishable_entities( + collection.entities, + has_published=True, + ).count() + doc.update({ Fields.context_key: str(library_key), Fields.org: str(library_key.org), @@ -495,7 +508,10 @@ def searchable_doc_for_collection( Fields.description: collection.description, Fields.created: collection.created.timestamp(), Fields.modified: collection.modified.timestamp(), - Fields.num_children: collection.entities.count(), + Fields.num_children: draft_num_children, + Fields.published: { + Fields.published_num_children: published_num_children, + }, Fields.access_id: _meili_access_id_from_context_key(library_key), Fields.breadcrumbs: [{"display_name": collection.learning_package.title}], }) diff --git a/openedx/core/djangoapps/content/search/tests/test_api.py b/openedx/core/djangoapps/content/search/tests/test_api.py index 990226f343cf..0aa762fd187f 100644 --- a/openedx/core/djangoapps/content/search/tests/test_api.py +++ b/openedx/core/djangoapps/content/search/tests/test_api.py @@ -198,6 +198,9 @@ def setUp(self): "created": created_date.timestamp(), "modified": created_date.timestamp(), "access_id": lib_access.id, + "published": { + "num_children": 0 + }, "breadcrumbs": [{"display_name": "Library"}], } @@ -472,6 +475,9 @@ def test_index_library_block_and_collections(self, mock_meilisearch): "created": created_date.timestamp(), "modified": created_date.timestamp(), "access_id": lib_access.id, + "published": { + "num_children": 0 + }, "breadcrumbs": [{"display_name": "Library"}], } doc_collection2_created = { @@ -487,6 +493,9 @@ def test_index_library_block_and_collections(self, mock_meilisearch): "created": created_date.timestamp(), "modified": created_date.timestamp(), "access_id": lib_access.id, + "published": { + "num_children": 0 + }, "breadcrumbs": [{"display_name": "Library"}], } doc_collection2_updated = { @@ -502,6 +511,9 @@ def test_index_library_block_and_collections(self, mock_meilisearch): "created": created_date.timestamp(), "modified": updated_date.timestamp(), "access_id": lib_access.id, + "published": { + "num_children": 0 + }, "breadcrumbs": [{"display_name": "Library"}], } doc_collection1_updated = { @@ -517,6 +529,9 @@ def test_index_library_block_and_collections(self, mock_meilisearch): "created": created_date.timestamp(), "modified": updated_date.timestamp(), "access_id": lib_access.id, + "published": { + "num_children": 0 + }, "breadcrumbs": [{"display_name": "Library"}], } doc_problem_with_collection1 = { diff --git a/openedx/core/djangoapps/content/search/tests/test_documents.py b/openedx/core/djangoapps/content/search/tests/test_documents.py index e0a604c24feb..603cc8d92f5e 100644 --- a/openedx/core/djangoapps/content/search/tests/test_documents.py +++ b/openedx/core/djangoapps/content/search/tests/test_documents.py @@ -443,5 +443,37 @@ def test_collection_with_library(self): 'tags': { 'taxonomy': ['Difficulty'], 'level0': ['Difficulty > Normal'] + }, + "published": { + "num_children": 0 + } + } + + def test_collection_with_published_library(self): + library_api.publish_changes(self.library.key) + + doc = searchable_doc_for_collection(self.library.key, self.collection.key) + doc.update(searchable_doc_tags_for_collection(self.library.key, self.collection.key)) + + assert doc == { + "id": "lib-collectionedx2012_falltoy_collection-d1d907a4", + "block_id": self.collection.key, + "usage_key": self.collection_usage_key, + "type": "collection", + "org": "edX", + "display_name": "Toy Collection", + "description": "my toy collection description", + "num_children": 1, + "context_key": "lib:edX:2012_Fall", + "access_id": self.library_access_id, + "breadcrumbs": [{"display_name": "some content_library"}], + "created": 1680674828.0, + "modified": 1680674828.0, + 'tags': { + 'taxonomy': ['Difficulty'], + 'level0': ['Difficulty > Normal'] + }, + "published": { + "num_children": 1 } } diff --git a/openedx/core/djangoapps/content_libraries/api.py b/openedx/core/djangoapps/content_libraries/api.py index fdfe2ff0efaf..85cd2f2c06e8 100644 --- a/openedx/core/djangoapps/content_libraries/api.py +++ b/openedx/core/djangoapps/content_libraries/api.py @@ -58,6 +58,7 @@ import logging import mimetypes + import attr import requests @@ -82,6 +83,7 @@ ContentLibraryData, LibraryBlockData, LibraryCollectionData, + ContentObjectChangedData, ) from openedx_events.content_authoring.signals import ( CONTENT_LIBRARY_CREATED, @@ -91,6 +93,7 @@ LIBRARY_BLOCK_DELETED, LIBRARY_BLOCK_UPDATED, LIBRARY_COLLECTION_UPDATED, + CONTENT_OBJECT_ASSOCIATIONS_CHANGED, ) from openedx_learning.api import authoring as authoring_api from openedx_learning.api.authoring_models import ( @@ -683,7 +686,11 @@ def _get_library_component_tags_count(library_key) -> dict: return get_object_tag_counts(library_key_pattern, count_implicit=True) -def get_library_components(library_key, text_search=None, block_types=None) -> QuerySet[Component]: +def get_library_components( + library_key, + text_search=None, + block_types=None, +) -> QuerySet[Component]: """ Get the library components and filter. @@ -699,6 +706,7 @@ def get_library_components(library_key, text_search=None, block_types=None) -> Q type_names=block_types, draft_title=text_search, ) + return components @@ -1013,7 +1021,6 @@ def import_staged_content_from_user_clipboard(library_key: LibraryLocatorV2, use component_version.pk, content.id, key=filename, - learner_downloadable=True, ) # Emit library block created event @@ -1083,7 +1090,6 @@ def _create_component_for_block(content_lib, usage_key, user_id=None): component_version.pk, content.id, key="block.xml", - learner_downloadable=False ) return component_version @@ -1094,15 +1100,31 @@ def delete_library_block(usage_key, remove_from_parent=True): Delete the specified block from this library (soft delete). """ component = get_component_from_usage_key(usage_key) + library_key = usage_key.context_key + affected_collections = authoring_api.get_entity_collections(component.learning_package_id, component.key) + authoring_api.soft_delete_draft(component.pk) LIBRARY_BLOCK_DELETED.send_event( library_block=LibraryBlockData( - library_key=usage_key.context_key, + library_key=library_key, usage_key=usage_key ) ) + # For each collection, trigger LIBRARY_COLLECTION_UPDATED signal and set background=True to trigger + # collection indexing asynchronously. + # + # To delete the component on collections + for collection in affected_collections: + LIBRARY_COLLECTION_UPDATED.send_event( + library_collection=LibraryCollectionData( + library_key=library_key, + collection_key=collection.key, + background=True, + ) + ) + def get_library_block_static_asset_files(usage_key) -> list[LibraryXBlockStaticFile]: """ @@ -1176,26 +1198,11 @@ def add_library_block_static_asset_file(usage_key, file_path, file_content, user component = get_component_from_usage_key(usage_key) - media_type_str, _encoding = mimetypes.guess_type(file_path) - # We use "application/octet-stream" as a generic fallback media type, per - # RFC 2046: https://datatracker.ietf.org/doc/html/rfc2046 - # TODO: This probably makes sense to push down to openedx-learning? - media_type_str = media_type_str or "application/octet-stream" - - now = datetime.now(tz=timezone.utc) - with transaction.atomic(): - media_type = authoring_api.get_or_create_media_type(media_type_str) - content = authoring_api.get_or_create_file_content( - component.publishable_entity.learning_package.id, - media_type.id, - data=file_content, - created=now, - ) component_version = authoring_api.create_next_component_version( component.pk, - content_to_replace={file_path: content.id}, - created=now, + content_to_replace={file_path: file_content}, + created=datetime.now(tz=timezone.utc), created_by=user.id if user else None, ) transaction.on_commit( @@ -1220,7 +1227,7 @@ def add_library_block_static_asset_file(usage_key, file_path, file_content, user return LibraryXBlockStaticFile( path=file_path, url=site_root_url + local_path, - size=content.size, + size=len(file_content), ) @@ -1334,6 +1341,39 @@ def revert_changes(library_key): ) ) + # For each collection, trigger LIBRARY_COLLECTION_UPDATED signal and set background=True to trigger + # collection indexing asynchronously. + # + # This is to update component counts in all library collections, + # because there may be components that have been discarded in the revert. + for collection in authoring_api.get_collections(learning_package.id): + LIBRARY_COLLECTION_UPDATED.send_event( + library_collection=LibraryCollectionData( + library_key=library_key, + collection_key=collection.key, + background=True, + ) + ) + + # Reindex components that are in collections + # + # Use case: When a component that was within a collection has been deleted + # and the changes are reverted, the component should appear in the + # collection again. + components_in_collections = authoring_api.get_components( + learning_package.id, draft=True, namespace='xblock.v1', + ).filter(publishable_entity__collections__isnull=False) + + for component in components_in_collections: + usage_key = library_component_usage_key(library_key, component) + + CONTENT_OBJECT_ASSOCIATIONS_CHANGED.send_event( + content_object=ContentObjectChangedData( + object_id=str(usage_key), + changes=["collections"], + ), + ) + def create_library_collection( library_key: LibraryLocatorV2, diff --git a/openedx/core/djangoapps/content_libraries/models.py b/openedx/core/djangoapps/content_libraries/models.py index 4a210223cc29..61e28b944851 100644 --- a/openedx/core/djangoapps/content_libraries/models.py +++ b/openedx/core/djangoapps/content_libraries/models.py @@ -89,6 +89,8 @@ class ContentLibrary(models.Model): re-imported on another Open edX instance should be kept in Learning Core. This model in Studio should only be used to track settings specific to this Open edX instance, like who has permission to edit this content library. + + .. no_pii: """ objects: ContentLibraryManager[ContentLibrary] = ContentLibraryManager() @@ -183,6 +185,8 @@ def __str__(self): class ContentLibraryPermission(models.Model): """ Row recording permissions for a content library + + .. no_pii: """ library = models.ForeignKey(ContentLibrary, on_delete=models.CASCADE, related_name="permission_grants") # One of the following must be set (but not both): @@ -226,6 +230,8 @@ def __str__(self): class ContentLibraryBlockImportTask(models.Model): """ Model of a task to import blocks from an external source (e.g. modulestore). + + .. no_pii: """ library = models.ForeignKey( @@ -331,6 +337,8 @@ class LtiProfile(models.Model): Unless Anonymous, this should be a unique representation of the LTI subject (as per the client token ``sub`` identify claim) that initiated an LTI launch through Content Libraries. + + .. no_pii: """ objects = LtiProfileManager() @@ -453,6 +461,8 @@ class LtiGradedResource(models.Model): launch. This model links the profile that launched the resource with the resource itself, allowing identifcation of the link through its usage key string and user id. + + .. no_pii: """ objects = LtiGradedResourceManager() diff --git a/openedx/core/djangoapps/content_libraries/tests/base.py b/openedx/core/djangoapps/content_libraries/tests/base.py index 7f1664e6c7fc..638c053f62c3 100644 --- a/openedx/core/djangoapps/content_libraries/tests/base.py +++ b/openedx/core/djangoapps/content_libraries/tests/base.py @@ -86,11 +86,10 @@ def assertDictContainsEntries(self, big_dict, subset_dict): """ Assert that the first dict contains at least all of the same entries as the second dict. - - Like python 2's assertDictContainsSubset, but with the arguments in the - correct order. """ - assert big_dict.items() >= subset_dict.items() + for key, value in subset_dict.items(): + assert key in big_dict, f"Missing key: {key}" + assert big_dict[key] == value, f"Value for key {key} does not match: expected {value}, got {big_dict[key]}" def assertOrderEqual(self, libraries_list, expected_order): """ diff --git a/openedx/core/djangoapps/content_libraries/tests/test_api.py b/openedx/core/djangoapps/content_libraries/tests/test_api.py index c526e7b1a1f3..7be3e592ba9d 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_api.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_api.py @@ -561,3 +561,155 @@ def test_set_library_component_collections(self): }, collection_update_event_receiver.call_args_list[1].kwargs, ) + + def test_delete_library_block(self): + api.update_library_collection_components( + self.lib1.library_key, + self.col1.key, + usage_keys=[ + UsageKey.from_string(self.lib1_problem_block["id"]), + UsageKey.from_string(self.lib1_html_block["id"]), + ], + ) + + event_receiver = mock.Mock() + LIBRARY_COLLECTION_UPDATED.connect(event_receiver) + + api.delete_library_block(UsageKey.from_string(self.lib1_problem_block["id"])) + + assert event_receiver.call_count == 1 + self.assertDictContainsSubset( + { + "signal": LIBRARY_COLLECTION_UPDATED, + "sender": None, + "library_collection": LibraryCollectionData( + self.lib1.library_key, + collection_key=self.col1.key, + background=True, + ), + }, + event_receiver.call_args_list[0].kwargs, + ) + + def test_add_component_and_revert(self): + # Add component and publish + api.update_library_collection_components( + self.lib1.library_key, + self.col1.key, + usage_keys=[ + UsageKey.from_string(self.lib1_problem_block["id"]), + ], + ) + api.publish_changes(self.lib1.library_key) + + # Add component and revert + api.update_library_collection_components( + self.lib1.library_key, + self.col1.key, + usage_keys=[ + UsageKey.from_string(self.lib1_html_block["id"]), + ], + ) + + event_receiver = mock.Mock() + CONTENT_OBJECT_ASSOCIATIONS_CHANGED.connect(event_receiver) + collection_update_event_receiver = mock.Mock() + LIBRARY_COLLECTION_UPDATED.connect(collection_update_event_receiver) + + api.revert_changes(self.lib1.library_key) + + assert collection_update_event_receiver.call_count == 1 + assert event_receiver.call_count == 2 + self.assertDictContainsSubset( + { + "signal": LIBRARY_COLLECTION_UPDATED, + "sender": None, + "library_collection": LibraryCollectionData( + self.lib1.library_key, + collection_key=self.col1.key, + background=True, + ), + }, + collection_update_event_receiver.call_args_list[0].kwargs, + ) + self.assertDictContainsSubset( + { + "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, + "sender": None, + "content_object": ContentObjectChangedData( + object_id=str(self.lib1_problem_block["id"]), + changes=["collections"], + ), + }, + event_receiver.call_args_list[0].kwargs, + ) + self.assertDictContainsSubset( + { + "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, + "sender": None, + "content_object": ContentObjectChangedData( + object_id=str(self.lib1_html_block["id"]), + changes=["collections"], + ), + }, + event_receiver.call_args_list[1].kwargs, + ) + + def test_delete_component_and_revert(self): + # Add components and publish + api.update_library_collection_components( + self.lib1.library_key, + self.col1.key, + usage_keys=[ + UsageKey.from_string(self.lib1_problem_block["id"]), + UsageKey.from_string(self.lib1_html_block["id"]) + ], + ) + api.publish_changes(self.lib1.library_key) + + # Delete component and revert + api.delete_library_block(UsageKey.from_string(self.lib1_problem_block["id"])) + + event_receiver = mock.Mock() + CONTENT_OBJECT_ASSOCIATIONS_CHANGED.connect(event_receiver) + collection_update_event_receiver = mock.Mock() + LIBRARY_COLLECTION_UPDATED.connect(collection_update_event_receiver) + + api.revert_changes(self.lib1.library_key) + + assert collection_update_event_receiver.call_count == 1 + assert event_receiver.call_count == 2 + self.assertDictContainsSubset( + { + "signal": LIBRARY_COLLECTION_UPDATED, + "sender": None, + "library_collection": LibraryCollectionData( + self.lib1.library_key, + collection_key=self.col1.key, + background=True, + ), + }, + collection_update_event_receiver.call_args_list[0].kwargs, + ) + self.assertDictContainsSubset( + { + "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, + "sender": None, + "content_object": ContentObjectChangedData( + object_id=str(self.lib1_problem_block["id"]), + changes=["collections"], + ), + }, + event_receiver.call_args_list[0].kwargs, + ) + self.assertDictContainsSubset( + { + "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, + "sender": None, + "content_object": ContentObjectChangedData( + object_id=str(self.lib1_html_block["id"]), + changes=["collections"], + ), + }, + event_receiver.call_args_list[1].kwargs, + ) diff --git a/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py b/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py index b74d16b678e2..18d4ec591604 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py @@ -1066,8 +1066,8 @@ def test_library_paste_clipboard(self): self._get_library_block_asset(pasted_usage_key, "static/hello.txt") # Compare the two text files - src_data = self.client.get(f"/library_assets/blocks/{usage_key}/static/hello.txt").content - dest_data = self.client.get(f"/library_assets/blocks/{pasted_usage_key}/static/hello.txt").content + src_data = self.client.get(f"/library_assets/blocks/{usage_key}/static/hello.txt").getvalue() + dest_data = self.client.get(f"/library_assets/blocks/{pasted_usage_key}/static/hello.txt").getvalue() assert src_data == dest_data # Check that the new block was created after the paste and it's content matches diff --git a/openedx/core/djangoapps/content_libraries/tests/test_runtime.py b/openedx/core/djangoapps/content_libraries/tests/test_runtime.py index 3b505d311162..5fe5bb4eb1fc 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_runtime.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_runtime.py @@ -2,12 +2,12 @@ Test the Learning-Core-based XBlock runtime and content libraries together. """ import json -from gettext import GNUTranslations -from django.test import TestCase from completion.test_utils import CompletionWaffleTestMixin from django.db import connections, transaction +from django.test import TestCase, override_settings from django.utils.text import slugify +import django.utils.translation from organizations.models import Organization from rest_framework.test import APIClient from xblock.core import XBlock @@ -140,18 +140,17 @@ def test_html_round_trip(self): assert olx_3 == olx_2 == canonical_olx -class ContentLibraryRuntimeTests(ContentLibraryContentTestMixin): +class ContentLibraryRuntimeTests(ContentLibraryContentTestMixin, TestCase): """ Basic tests of the Learning-Core-based XBlock runtime using XBlocks in a content library. """ - def test_dndv2_sets_translator(self): dnd_block_key = library_api.create_library_block(self.library.key, "drag-and-drop-v2", "dnd1").usage_key library_api.publish_changes(self.library.key) dnd_block = xblock_api.load_block(dnd_block_key, self.student_a) i18n_service = dnd_block.runtime.service(dnd_block, 'i18n') - assert isinstance(i18n_service.translator, GNUTranslations) + assert i18n_service.translator is django.utils.translation def test_has_score(self): """ @@ -169,8 +168,7 @@ def test_xblock_metadata(self): """ Test the XBlock metadata API """ - unit_block_key = library_api.create_library_block(self.library.key, "unit", "metadata-u1").usage_key - problem_key = library_api.create_library_block_child(unit_block_key, "problem", "metadata-p1").usage_key + problem_key = library_api.create_library_block(self.library.key, "problem", "metadata-p1").usage_key new_olx = """ @@ -192,14 +190,6 @@ def test_xblock_metadata(self): client = APIClient() client.login(username=self.student_a.username, password='edx') - # Check the metadata API for the unit: - metadata_view_result = client.get( - URL_BLOCK_METADATA_URL.format(block_key=unit_block_key), - {"include": "children,editable_children"}, - ) - assert metadata_view_result.data['children'] == [str(problem_key)] - assert metadata_view_result.data['editable_children'] == [str(problem_key)] - # Check the metadata API for the problem: metadata_view_result = client.get( URL_BLOCK_METADATA_URL.format(block_key=problem_key), @@ -226,8 +216,7 @@ def test_xblock_fields(self): client.login(username=self.staff_user.username, password='edx') # create/save a block using the library APIs first - unit_block_key = library_api.create_library_block(self.library.key, "unit", "fields-u1").usage_key - block_key = library_api.create_library_block_child(unit_block_key, "html", "fields-p1").usage_key + block_key = library_api.create_library_block(self.library.key, "html", "fields-p1").usage_key new_olx = """

This is some HTML.

@@ -250,14 +239,24 @@ def test_xblock_fields(self): } }, format='json') block_saved = xblock_api.load_block(block_key, self.staff_user) - assert block_saved.data == '\n

test

\n' + assert block_saved.data == '

test

' assert block_saved.display_name == 'New Display Name' +# EphemeralKeyValueStore requires a working cache, and the default test cache is a dummy cache. +@override_settings( + XBLOCK_RUNTIME_V2_EPHEMERAL_DATA_CACHE='default', + CACHES={ + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'LOCATION': 'edx_loc_mem_cache', + }, + }, +) # We can remove the line below to enable this in Studio once we implement a session-backed # field data store which we can use for both studio users and anonymous users @skip_unless_lms -class ContentLibraryXBlockUserStateTest(ContentLibraryContentTestMixin): +class ContentLibraryXBlockUserStateTest(ContentLibraryContentTestMixin, TestCase): """ Test that the Blockstore-based XBlock runtime can store and retrieve student state for XBlocks when learners access blocks directly in a library context, @@ -560,7 +559,7 @@ def test_i18n(self): @skip_unless_lms # No completion tracking in Studio -class ContentLibraryXBlockCompletionTest(ContentLibraryContentTestMixin, CompletionWaffleTestMixin): +class ContentLibraryXBlockCompletionTest(ContentLibraryContentTestMixin, CompletionWaffleTestMixin, TestCase): """ Test that the Blockstore-based XBlocks can track their completion status using the completion library. diff --git a/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py b/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py index b903a0ca977a..69f5cd2d797c 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_static_assets.py @@ -168,12 +168,12 @@ def test_anonymous_user(self): response = self.client.get( f"/library_assets/component_versions/{self.draft_component_version.uuid}/static/test.svg" ) - assert response.status_code == 403 + assert response.status_code == 401 def test_unauthorized_user(self): """User who is not a Content Library staff should not have access.""" self.client.logout() - student = UserFactory.create( + UserFactory.create( username="student", email="student@example.com", password="student-pass", diff --git a/openedx/core/djangoapps/content_libraries/urls.py b/openedx/core/djangoapps/content_libraries/urls.py index 5bf36162d56d..857126eef7c9 100644 --- a/openedx/core/djangoapps/content_libraries/urls.py +++ b/openedx/core/djangoapps/content_libraries/urls.py @@ -79,12 +79,12 @@ path('library_assets/', include([ path( 'component_versions//', - views.component_version_asset, + views.LibraryComponentAssetView.as_view(), name='library-assets', ), path( 'blocks//', - views.component_draft_asset, + views.LibraryComponentDraftAssetView.as_view(), name='library-draft-assets', ), ]) diff --git a/openedx/core/djangoapps/content_libraries/views.py b/openedx/core/djangoapps/content_libraries/views.py index 6325d9c4aeb4..e30e58e75a26 100644 --- a/openedx/core/djangoapps/content_libraries/views.py +++ b/openedx/core/djangoapps/content_libraries/views.py @@ -80,7 +80,6 @@ from django.utils.translation import gettext as _ from django.views.decorators.clickjacking import xframe_options_exempt from django.views.decorators.csrf import csrf_exempt -from django.views.decorators.http import require_safe from django.views.generic.base import TemplateResponseMixin, View from pylti1p3.contrib.django import DjangoCacheDataStorage, DjangoDbToolConf, DjangoMessageLaunch, DjangoOIDCLogin from pylti1p3.exception import LtiException, OIDCException @@ -1163,8 +1162,7 @@ def get(self, request): return JsonResponse(self.lti_tool_config.get_jwks(), safe=False) -@require_safe -def component_version_asset(request, component_version_uuid, asset_path): +def get_component_version_asset(request, component_version_uuid, asset_path): """ Serves static assets associated with particular Component versions. @@ -1201,7 +1199,6 @@ def component_version_asset(request, component_version_uuid, asset_path): component_version_uuid, asset_path, public=False, - learner_downloadable_only=False, ) # If there was any error, we return that response because it will have the @@ -1234,16 +1231,34 @@ def component_version_asset(request, component_version_uuid, asset_path): ) -@require_safe -def component_draft_asset(request, usage_key, asset_path): +@view_auth_classes() +class LibraryComponentAssetView(APIView): + """ + Serves static assets associated with particular Component versions. + """ + @convert_exceptions + def get(self, request, component_version_uuid, asset_path): + """ + GET API for fetching static asset for given component_version_uuid. + """ + return get_component_version_asset(request, component_version_uuid, asset_path) + + +@view_auth_classes() +class LibraryComponentDraftAssetView(APIView): """ Serves the draft version of static assets associated with a Library Component. - See `component_version_asset` for more details + See `get_component_version_asset` for more details """ - try: - component_version_uuid = api.get_component_from_usage_key(usage_key).versioning.draft.uuid - except ObjectDoesNotExist as exc: - raise Http404() from exc + @convert_exceptions + def get(self, request, usage_key, asset_path): + """ + Fetches component_version_uuid for given usage_key and returns component asset. + """ + try: + component_version_uuid = api.get_component_from_usage_key(usage_key).versioning.draft.uuid + except ObjectDoesNotExist as exc: + raise Http404() from exc - return component_version_asset(request, component_version_uuid, asset_path) + return get_component_version_asset(request, component_version_uuid, asset_path) diff --git a/openedx/core/djangoapps/content_tagging/models/base.py b/openedx/core/djangoapps/content_tagging/models/base.py index 8a232d3a7bf4..d799d8795194 100644 --- a/openedx/core/djangoapps/content_tagging/models/base.py +++ b/openedx/core/djangoapps/content_tagging/models/base.py @@ -16,6 +16,8 @@ class TaxonomyOrg(models.Model): We keep this as a separate class from ContentTaxonomy so that class can remain a proxy for Taxonomy, keeping the data models and usage simple. + + .. no_pii: """ class RelType(models.TextChoices): diff --git a/openedx/core/djangoapps/cors_csrf/tests/test_middleware.py b/openedx/core/djangoapps/cors_csrf/tests/test_middleware.py index a546cbbcd4bb..119824607ded 100644 --- a/openedx/core/djangoapps/cors_csrf/tests/test_middleware.py +++ b/openedx/core/djangoapps/cors_csrf/tests/test_middleware.py @@ -270,14 +270,13 @@ def _assert_cookie_sent(self, response, is_set): if is_set: assert self.COOKIE_NAME in response.cookies cookie_header = str(response.cookies[self.COOKIE_NAME]) - # lint-amnesty, pylint: disable=bad-option-value, unicode-format-string + expected = 'Set-Cookie: {name}={value}; Domain={domain};'.format( name=self.COOKIE_NAME, value=self.COOKIE_VALUE, domain=self.COOKIE_DOMAIN ) assert expected in cookie_header - # added lower function because in python 3 the value of cookie_header has Secure and secure in python 2 - assert 'Max-Age=31449600; Path=/; secure'.lower() in cookie_header.lower() + assert 'Max-Age=31449600; Path=/; Secure' in cookie_header else: assert self.COOKIE_NAME not in response.cookies diff --git a/openedx/core/djangoapps/course_live/models.py b/openedx/core/djangoapps/course_live/models.py index a871ec024447..c311c6fb3f2f 100644 --- a/openedx/core/djangoapps/course_live/models.py +++ b/openedx/core/djangoapps/course_live/models.py @@ -12,6 +12,8 @@ class CourseLiveConfiguration(TimeStampedModel): """ Associates a Course with a LTI provider and configuration + + .. no_pii: """ course_key = CourseKeyField(max_length=255, db_index=True, null=False) enabled = models.BooleanField( diff --git a/openedx/core/djangoapps/courseware_api/views.py b/openedx/core/djangoapps/courseware_api/views.py index 7f56133b3459..07e219f83d81 100644 --- a/openedx/core/djangoapps/courseware_api/views.py +++ b/openedx/core/djangoapps/courseware_api/views.py @@ -19,6 +19,7 @@ from rest_framework.response import Response from rest_framework.views import APIView +from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem from xmodule.modulestore.search import path_to_location @@ -594,30 +595,40 @@ def get(self, request, usage_key_string, *args, **kwargs): # lint-amnesty, pyli usage_key = UsageKey.from_string(usage_key_string) except InvalidKeyError as exc: raise NotFound(f"Invalid usage key: '{usage_key_string}'.") from exc + + staff_access = has_access(request.user, 'staff', usage_key.course_key) + is_preview = request.GET.get('preview', '0') == '1' _, request.user = setup_masquerade( request, usage_key.course_key, - staff_access=has_access(request.user, 'staff', usage_key.course_key), + staff_access=staff_access, reset_masquerade_data=True, ) - sequence, _ = get_block_by_usage_id( - self.request, - str(usage_key.course_key), - str(usage_key), - disable_staff_debug_info=True, - will_recheck_access=True) + branch_type = ( + ModuleStoreEnum.Branch.draft_preferred + ) if is_preview and staff_access else ( + ModuleStoreEnum.Branch.published_only + ) + + with modulestore().branch_setting(branch_type, usage_key.course_key): + sequence, _ = get_block_by_usage_id( + self.request, + str(usage_key.course_key), + str(usage_key), + disable_staff_debug_info=True, + will_recheck_access=True) - if not hasattr(sequence, 'get_metadata'): - # Looks like we were asked for metadata on something that is not a sequence (or section). - return Response(status=status.HTTP_422_UNPROCESSABLE_ENTITY) + if not hasattr(sequence, 'get_metadata'): + # Looks like we were asked for metadata on something that is not a sequence (or section). + return Response(status=status.HTTP_422_UNPROCESSABLE_ENTITY) - view = STUDENT_VIEW - if request.user.is_anonymous: - view = PUBLIC_VIEW + view = STUDENT_VIEW + if request.user.is_anonymous: + view = PUBLIC_VIEW - context = {'specific_masquerade': is_masquerading_as_specific_student(request.user, usage_key.course_key)} - return Response(sequence.get_metadata(view=view, context=context)) + context = {'specific_masquerade': is_masquerading_as_specific_student(request.user, usage_key.course_key)} + return Response(sequence.get_metadata(view=view, context=context)) class Resume(DeveloperErrorViewMixin, APIView): diff --git a/openedx/core/djangoapps/crawlers/models.py b/openedx/core/djangoapps/crawlers/models.py index b7f9d23d6544..fe0ae3193b0f 100644 --- a/openedx/core/djangoapps/crawlers/models.py +++ b/openedx/core/djangoapps/crawlers/models.py @@ -42,16 +42,14 @@ def is_crawler(cls, request): # If there was no user agent detected or no crawler agents configured, # then just return False. - if (not req_user_agent) or (not crawler_agents): + if not req_user_agent or not crawler_agents: return False - # The crawler_agents list we pull from our model always has unicode objects, but the - # req_user_agent we get from HTTP headers ultimately comes to us via WSGI. That - # value is an ISO-8859-1 encoded byte string in Python 2.7 (and in the HTTP spec), but - # it will be a unicode str when we move to Python 3.x. This code should work under - # either version. + # Decode req_user_agent if it's bytes, so we can work with consistent string types. if isinstance(req_user_agent, bytes): - crawler_agents = [crawler_agent.encode('iso-8859-1') for crawler_agent in crawler_agents] + req_user_agent = req_user_agent.decode('iso-8859-1') + + crawler_agents = [crawler_agent.strip() for crawler_agent in crawler_agents] # We perform prefix matching of the crawler agent here so that we don't # have to worry about version bumps. diff --git a/openedx/core/djangoapps/discussions/models.py b/openedx/core/djangoapps/discussions/models.py index 9d97c9051397..f47b661c9ad2 100644 --- a/openedx/core/djangoapps/discussions/models.py +++ b/openedx/core/djangoapps/discussions/models.py @@ -318,6 +318,8 @@ def get_supported_providers() -> List[str]: class ProviderFilter(StackedConfigurationModel): """ Associate allow/deny-lists of discussions providers with courses/orgs + + .. no_pii: """ allow = ListCharField( @@ -406,6 +408,8 @@ def get_available_providers(cls, course_key: CourseKey) -> List[str]: class DiscussionsConfiguration(TimeStampedModel): """ Associates a learning context with discussion provider and configuration + + .. no_pii: """ context_key = LearningContextKeyField( @@ -554,6 +558,8 @@ def lti_discussion_enabled(cls, course_key: CourseKey) -> bool: class DiscussionTopicLink(models.Model): """ A model linking discussion topics ids to the part of a course they are linked to. + + ..no_pii: """ context_key = LearningContextKeyField( db_index=True, diff --git a/openedx/core/djangoapps/notifications/base_notification.py b/openedx/core/djangoapps/notifications/base_notification.py index 795aecef795b..91a1635aec10 100644 --- a/openedx/core/djangoapps/notifications/base_notification.py +++ b/openedx/core/djangoapps/notifications/base_notification.py @@ -65,6 +65,8 @@ 'push': False, 'non_editable': [], 'content_template': _('<{p}><{strong}>{username} posted <{strong}>{post_title}'), + 'grouped_content_template': _('<{p}><{strong}>{replier_name} and others started new discussions' + ''), 'content_context': { 'post_title': 'Post title', 'username': 'Post author name', diff --git a/openedx/core/djangoapps/notifications/email/events.py b/openedx/core/djangoapps/notifications/email/events.py index 165539a018cb..a575d78eaeb5 100644 --- a/openedx/core/djangoapps/notifications/email/events.py +++ b/openedx/core/djangoapps/notifications/email/events.py @@ -12,19 +12,29 @@ EMAIL_DIGEST_SENT = "edx.notifications.email_digest" -def send_user_email_digest_sent_event(user, cadence_type, notifications): +def send_user_email_digest_sent_event(user, cadence_type, notifications, message_context): """ Sends tracker and segment email for user email digest """ notification_breakdown = {key: 0 for key in COURSE_NOTIFICATION_APPS.keys()} for notification in notifications: notification_breakdown[notification.app_name] += 1 + + truncated_count = {} + email_content = message_context.get("email_content", []) + for app in email_content: + truncated_count[app.get("title", "")] = { + "total": app.get("total", -1), + "remaining_count": app.get("remaining_count", -1), + } + event_data = { "username": user.username, "email": user.email, "cadence_type": cadence_type, "total_notifications_count": len(notifications), "count_breakdown": notification_breakdown, + "truncated_count": truncated_count, "notification_ids": [notification.id for notification in notifications], "send_at": str(datetime.datetime.now()) } diff --git a/openedx/core/djangoapps/notifications/email/tasks.py b/openedx/core/djangoapps/notifications/email/tasks.py index 0d450fe9a917..c2e0a2fa375d 100644 --- a/openedx/core/djangoapps/notifications/email/tasks.py +++ b/openedx/core/djangoapps/notifications/email/tasks.py @@ -103,7 +103,7 @@ def send_digest_email_to_user(user, cadence_type, start_date, end_date, course_l ).personalize(recipient, course_language, message_context) message = add_headers_to_email_message(message, message_context) ace.send(message) - send_user_email_digest_sent_event(user, cadence_type, notifications) + send_user_email_digest_sent_event(user, cadence_type, notifications, message_context) logger.info(f' Email sent to {user.username} ==Temp Log==') diff --git a/openedx/core/djangoapps/notifications/email/tests/test_utils.py b/openedx/core/djangoapps/notifications/email/tests/test_utils.py index 1f3da983a020..6c7f5b7144cf 100644 --- a/openedx/core/djangoapps/notifications/email/tests/test_utils.py +++ b/openedx/core/djangoapps/notifications/email/tests/test_utils.py @@ -148,8 +148,18 @@ def test_email_digest_context(self, digest_frequency): {'title': 'Updates', 'count': 1}, ] expected_email_content = [ - {'title': 'Discussion', 'help_text': '', 'help_text_url': '', 'notifications': [discussion_notification]}, - {'title': 'Updates', 'help_text': '', 'help_text_url': '', 'notifications': [update_notification]} + { + 'title': 'Discussion', 'help_text': '', 'help_text_url': '', + 'notifications': [discussion_notification], + 'total': 1, 'show_remaining_count': False, 'remaining_count': 0, + 'url': 'http://learner-home-mfe/?showNotifications=true&app=discussion' + }, + { + 'title': 'Updates', 'help_text': '', 'help_text_url': '', + 'notifications': [update_notification], + 'total': 1, 'show_remaining_count': False, 'remaining_count': 0, + 'url': 'http://learner-home-mfe/?showNotifications=true&app=updates' + } ] assert context['start_date'] == expected_start_date assert context['end_date'] == 'Sunday, Mar 24' diff --git a/openedx/core/djangoapps/notifications/email/utils.py b/openedx/core/djangoapps/notifications/email/utils.py index d855494012ea..9fd761785e5a 100644 --- a/openedx/core/djangoapps/notifications/email/utils.py +++ b/openedx/core/djangoapps/notifications/email/utils.py @@ -130,17 +130,29 @@ def create_email_digest_context(app_notifications_dict, username, start_date, en } for key, value in app_notifications_dict.items() ]) - email_content = [ - { + + email_content = [] + notifications_in_app = 5 + for key, value in app_notifications_dict.items(): + total = value['count'] + app_content = { 'title': value['title'], 'help_text': value.get('help_text', ''), 'help_text_url': value.get('help_text_url', ''), 'notifications': add_additional_attributes_to_notifications( value.get('notifications', []), courses_data=courses_data - ) + ), + 'total': total, + 'show_remaining_count': False, + 'remaining_count': 0, + 'url': f'{settings.LEARNER_HOME_MICROFRONTEND_URL}/?showNotifications=true&app={key}' } - for key, value in app_notifications_dict.items() - ] + if total > notifications_in_app: + app_content['notifications'] = app_content['notifications'][:notifications_in_app] + app_content['show_remaining_count'] = True + app_content['remaining_count'] = total - notifications_in_app + email_content.append(app_content) + context.update({ "start_date": start_date_str, "end_date": end_date_str, @@ -295,6 +307,7 @@ def filter_notification_with_email_enabled_preferences(notifications, preference for notification in notifications: if notification.notification_type in enabled_course_prefs[notification.course_id]: filtered_notifications.append(notification) + filtered_notifications.sort(key=lambda elem: elem.created, reverse=True) return filtered_notifications diff --git a/openedx/core/djangoapps/notifications/grouping_notifications.py b/openedx/core/djangoapps/notifications/grouping_notifications.py index b24baeb33136..3c4688b5ed53 100644 --- a/openedx/core/djangoapps/notifications/grouping_notifications.py +++ b/openedx/core/djangoapps/notifications/grouping_notifications.py @@ -77,13 +77,35 @@ def group(self, new_notification, old_notification): if not context.get('grouped'): context['replier_name_list'] = [context['replier_name']] context['grouped_count'] = 1 - context['grouped'] = True + context['grouped'] = True context['replier_name_list'].append(new_notification.content_context['replier_name']) context['grouped_count'] += 1 context['email_content'] = new_notification.content_context.get('email_content', '') return context +@NotificationRegistry.register('new_discussion_post') +class NewPostGrouper(BaseNotificationGrouper): + """ + Groups new post notifications based on the author name. + """ + + def group(self, new_notification, old_notification): + """ + Groups new post notifications based on the author name. + """ + if ( + old_notification.content_context['username'] == new_notification.content_context['username'] + and not old_notification.content_context.get('grouped', False) + ): + return {**new_notification.content_context} + return { + **old_notification.content_context, + "grouped": True, + "replier_name": new_notification.content_context["replier_name"] + } + + def group_user_notifications(new_notification: Notification, old_notification: Notification): """ Groups user notification based on notification type and group_id @@ -93,9 +115,9 @@ def group_user_notifications(new_notification: Notification, old_notification: N if grouper_class: old_notification.content_context = grouper_class.group(new_notification, old_notification) - old_notification.content_context['grouped'] = True old_notification.web = old_notification.web or new_notification.web old_notification.email = old_notification.email or new_notification.email + old_notification.content_url = new_notification.content_url old_notification.last_read = None old_notification.last_seen = None old_notification.created = utc.localize(datetime.datetime.now()) diff --git a/openedx/core/djangoapps/notifications/templates/notifications/digest_content.html b/openedx/core/djangoapps/notifications/templates/notifications/digest_content.html index df1de6171ae3..51966f96f574 100644 --- a/openedx/core/djangoapps/notifications/templates/notifications/digest_content.html +++ b/openedx/core/djangoapps/notifications/templates/notifications/digest_content.html @@ -1,5 +1,5 @@ {% for notification_app in email_content %} -

+

{{ notification_app.title }}

{% if notification_app.help_text %} @@ -29,7 +29,7 @@

-
+
{{ notification.email_content | truncatechars_html:600 | safe }}
@@ -56,5 +56,13 @@

+

+ {% if notification_app.show_remaining_count %} +

+ + + {{ notification_app.remaining_count }} more + +

+ {% endif %} +

{% endfor %} diff --git a/openedx/core/djangoapps/notifications/templates/notifications/digest_footer.html b/openedx/core/djangoapps/notifications/templates/notifications/digest_footer.html index 4fa903d127ac..34f4bf09d808 100644 --- a/openedx/core/djangoapps/notifications/templates/notifications/digest_footer.html +++ b/openedx/core/djangoapps/notifications/templates/notifications/digest_footer.html @@ -1,4 +1,4 @@ - +
- @@ -28,7 +28,7 @@ diff --git a/openedx/core/djangoapps/notifications/templates/notifications/edx_ace/email_digest/email/body.html b/openedx/core/djangoapps/notifications/templates/notifications/edx_ace/email_digest/email/body.html index a15f4f6ebfed..4d4daa7ca2a3 100644 --- a/openedx/core/djangoapps/notifications/templates/notifications/edx_ace/email_digest/email/body.html +++ b/openedx/core/djangoapps/notifications/templates/notifications/edx_ace/email_digest/email/body.html @@ -3,7 +3,7 @@
-
diff --git a/openedx/core/djangoapps/notifications/templates/notifications/digest_header.html b/openedx/core/djangoapps/notifications/templates/notifications/digest_header.html index 1f22ced20049..84a702d4c29e 100644 --- a/openedx/core/djangoapps/notifications/templates/notifications/digest_header.html +++ b/openedx/core/djangoapps/notifications/templates/notifications/digest_header.html @@ -19,7 +19,7 @@
+ {{ digest_frequency }} email digest
- + {{update.title}}
+
diff --git a/openedx/core/djangoapps/notifications/tests/test_notification_grouping.py b/openedx/core/djangoapps/notifications/tests/test_notification_grouping.py index e46cde7e8378..fea954a0eabb 100644 --- a/openedx/core/djangoapps/notifications/tests/test_notification_grouping.py +++ b/openedx/core/djangoapps/notifications/tests/test_notification_grouping.py @@ -12,7 +12,7 @@ NotificationRegistry, NewCommentGrouper, group_user_notifications, - get_user_existing_notifications + get_user_existing_notifications, NewPostGrouper ) from openedx.core.djangoapps.notifications.models import Notification @@ -102,6 +102,47 @@ def test_group_email_content(self): self.assertEqual(content_context['email_content'], 'new content') +class TestNewPostGrouper(unittest.TestCase): + """ + Tests for the NewPostGrouper class + """ + + def test_group(self): + """ + Test that the function groups new post notifications based on the author name + """ + new_notification = MagicMock(spec=Notification) + old_notification = MagicMock(spec=Notification) + old_notification.content_context = { + 'author_name': 'User1', + 'username': 'User1' + } + + updated_context = NewPostGrouper().group(new_notification, old_notification) + + self.assertTrue(updated_context['grouped']) + self.assertEqual(updated_context['replier_name'], new_notification.content_context['replier_name']) + + def test_new_post_with_same_user(self): + """ + Test that the function does not group notifications with same authors if notification is not + already grouped + """ + new_notification = MagicMock(spec=Notification) + old_notification = MagicMock(spec=Notification) + old_notification.content_context = { + 'username': 'User1', + 'grouped': False + } + new_notification.content_context = { + 'username': 'User1', + } + + updated_context = NewPostGrouper().group(new_notification, old_notification) + + self.assertFalse(updated_context.get('grouped', False)) + + class TestGroupUserNotifications(unittest.TestCase): """ Tests for the group_user_notifications function diff --git a/openedx/core/djangoapps/programs/models.py b/openedx/core/djangoapps/programs/models.py index 302ad553f245..e708066813b8 100644 --- a/openedx/core/djangoapps/programs/models.py +++ b/openedx/core/djangoapps/programs/models.py @@ -72,8 +72,14 @@ def get(cls, program_uuid): class ProgramLiveConfiguration(AbstractProgramLTIConfiguration): + """ + .. no_pii: + """ history = HistoricalRecords() class ProgramDiscussionsConfiguration(AbstractProgramLTIConfiguration): + """ + .. no_pii: + """ history = HistoricalRecords() diff --git a/openedx/core/djangoapps/user_api/accounts/utils.py b/openedx/core/djangoapps/user_api/accounts/utils.py index 3cc03c02ed5d..826dbd42cd13 100644 --- a/openedx/core/djangoapps/user_api/accounts/utils.py +++ b/openedx/core/djangoapps/user_api/accounts/utils.py @@ -51,11 +51,11 @@ def format_social_link(platform_name, new_social_link): """ Given a user's social link, returns a safe absolute url for the social link. - Returns the following based on the provided new_social_link: - 1) Given an empty string, returns '' - 1) Given a valid username, return 'https://www.[platform_name_base][username]' - 2) Given a valid URL, return 'https://www.[platform_name_base][username]' - 3) Given anything unparseable, returns None + Returns: + - An empty string if `new_social_link` is empty. + - A formatted URL if `new_social_link` is a username. + - Returns `new_social_link` if it is a valid URL. + - None for unparseable inputs. """ # Blank social links should return '' or None as was passed in. if not new_social_link: diff --git a/openedx/core/djangoapps/user_api/learner_skill_levels/__init__.py b/openedx/core/djangoapps/user_api/learner_skill_levels/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/openedx/core/djangoapps/user_api/learner_skill_levels/api.py b/openedx/core/djangoapps/user_api/learner_skill_levels/api.py deleted file mode 100644 index ea1714cb6a36..000000000000 --- a/openedx/core/djangoapps/user_api/learner_skill_levels/api.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -APIs for learner skill levels. -""" -from .utils import get_skills_score, calculate_user_skill_score, generate_skill_score_mapping - - -def get_learner_skill_levels(user, top_categories): - """ - Evaluates learner's skill levels in the given job category. Only considers skills for the categories - and not their sub-categories. - - Params: - user: user for each score is being calculated. - top_categories (List, string): A list of fields (as strings) of job categories and their skills. - Returns: - top_categories: Categories with scores appended to skills. - """ - - # get a skill to score mapping for every course user has passed - skill_score_mapping = generate_skill_score_mapping(user) - for skill_category in top_categories: - category_skills = skill_category['skills'] - get_skills_score(category_skills, skill_score_mapping) - skill_category['user_score'] = calculate_user_skill_score(category_skills) - skill_category['edx_average_score'] = None - sub_categories = skill_category['skills_subcategories'] - for sub_category in sub_categories: - subcategory_skills = sub_category['skills'] - get_skills_score(subcategory_skills, skill_score_mapping) - - return top_categories diff --git a/openedx/core/djangoapps/user_api/learner_skill_levels/constants.py b/openedx/core/djangoapps/user_api/learner_skill_levels/constants.py deleted file mode 100644 index 225dc1faba14..000000000000 --- a/openedx/core/djangoapps/user_api/learner_skill_levels/constants.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Constants for learner skill levels app. -""" -LEVEL_TYPE_SCORE_MAPPING = { - 'Introductory': 1, - 'Intermediate': 2, - 'Advanced': 3 -} diff --git a/openedx/core/djangoapps/user_api/learner_skill_levels/tests/__init__.py b/openedx/core/djangoapps/user_api/learner_skill_levels/tests/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/openedx/core/djangoapps/user_api/learner_skill_levels/tests/test_utils.py b/openedx/core/djangoapps/user_api/learner_skill_levels/tests/test_utils.py deleted file mode 100644 index fa90feb3c309..000000000000 --- a/openedx/core/djangoapps/user_api/learner_skill_levels/tests/test_utils.py +++ /dev/null @@ -1,219 +0,0 @@ -""" Unit tests for Learner Skill Levels utilities. """ - -import ddt -from collections import defaultdict -from unittest import mock - -from rest_framework.test import APIClient - -from common.djangoapps.student.tests.factories import UserFactory - -from openedx.core.djangoapps.catalog.tests.mixins import CatalogIntegrationMixin -from openedx.core.djangoapps.user_api.learner_skill_levels.utils import ( - calculate_user_skill_score, - generate_skill_score_mapping, - get_base_url, - get_job_holder_usernames, - get_skills_score, - get_top_skill_categories_for_job, - update_category_user_scores_map, - update_edx_average_score, -) -from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase - -from .testutils import ( - DUMMY_CATEGORIES_RESPONSE, - DUMMY_CATEGORIES_WITH_SCORES, - DUMMY_USERNAMES_RESPONSE, - DUMMY_COURSE_DATA_RESPONSE, - DUMMY_USER_SCORES_MAP, -) - - -@ddt.ddt -class LearnerSkillLevelsUtilsTests(SharedModuleStoreTestCase, CatalogIntegrationMixin): - """ - Test LearnerSkillLevel utilities. - """ - SERVICE_USERNAME = 'catalog_service_username' - - def setUp(self): - """ - Unit tests setup. - """ - super().setUp() - - self.client = APIClient() - self.service_user = UserFactory(username=self.SERVICE_USERNAME) - self.catalog_integration = self.create_catalog_integration() - - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.utils.get_course_run_ids') - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.utils.get_course_run_data') - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.utils.get_course_data') - def test_generate_skill_score_mapping( - self, - mock_get_course_data, - mock_get_course_run_data, - mock_get_course_run_ids, - ): - """ - Test that skill-score mapping is returned in correct format. - """ - user = UserFactory(username='edX') - mock_get_course_run_ids.return_value = ['course-v1:AWS+OTP-AWSD12'] - mock_get_course_run_data.return_value = {'course': 'AWS+OTP'} - mock_get_course_data.return_value = DUMMY_COURSE_DATA_RESPONSE - result = generate_skill_score_mapping(user) - expected_response = {"python": 3, "MongoDB": 3, "Data Science": 3} - assert result == expected_response - - @ddt.data( - ([], 0.0), - ( - [ - {"id": 1, "name": "Financial Management", "score": None}, - {"id": 2, "name": "Fintech", "score": None}, - ], 0.0 - ), - ( - [ - {"id": 1, "name": "Financial Management", "score": None}, - {"id": 2, "name": "Fintech", "score": None}, - ], 0.0 - ), - ( - [ - {"id": 1, "name": "Financial Management", "score": 3}, - {"id": 2, "name": "Fintech", "score": 2}, - ], 0.8 - ), - ) - @ddt.unpack - def test_calculate_user_skill_score(self, skills_with_score, expected): - """ - Test that skill-score mapping is returned in correct format. - """ - - result = calculate_user_skill_score(skills_with_score) - assert result == expected - - @ddt.data( - ([], {"Financial Management": 1, "Fintech": 3}, []), - ( - [ - {"id": 1, "name": "Financial Management"}, - {"id": 2, "name": "Fintech"}, - ], - { - "Financial Management": 1, - "Fintech": 3 - }, - [ - {"id": 1, "name": "Financial Management", "score": 1}, - {"id": 2, "name": "Fintech", "score": 3}, - ], - ), - ( - [ - {"id": 1, "name": "Financial Management"}, - {"id": 2, "name": "Fintech"}, - ], - {}, - [ - {"id": 1, "name": "Financial Management", "score": None}, - {"id": 2, "name": "Fintech", "score": None}, - ], - ), - ( - [ - {"id": 1, "name": "Financial Management"}, - {"id": 2, "name": "Fintech"}, - ], - { - "Python": 1, - "AI": 3 - }, - [ - {"id": 1, "name": "Financial Management", "score": None}, - {"id": 2, "name": "Fintech", "score": None}, - ], - ), - ) - @ddt.unpack - def test_get_skills_score(self, skills, learner_skill_score, expected): - """ - Test that skill-score mapping is returned in correct format. - """ - get_skills_score(skills, learner_skill_score) - assert skills == expected - - def test_update_category_user_scores_map(self): - """ - Test that skill-score mapping is returned in correct format. - """ - category_user_scores_map = defaultdict(list) - update_category_user_scores_map(DUMMY_CATEGORIES_WITH_SCORES["skill_categories"], category_user_scores_map) - expected = {"Information Technology": [0.8], "Finance": [0.3]} - assert category_user_scores_map == expected - - def test_update_edx_average_score(self): - """ - Test that skill-score mapping is returned in correct format. - """ - update_edx_average_score(DUMMY_CATEGORIES_WITH_SCORES["skill_categories"], DUMMY_USER_SCORES_MAP) - assert DUMMY_CATEGORIES_WITH_SCORES["skill_categories"][0]["edx_average_score"] == 0.4 - assert DUMMY_CATEGORIES_WITH_SCORES["skill_categories"][1]["edx_average_score"] == 0.5 - - @ddt.data( - ("http://localhost:18000/api/", "http://localhost:18000"), - ("http://localhost:18000/", "http://localhost:18000"), - ) - @ddt.unpack - def test_get_base_url(self, source_url, expected): - """ - Test that base url is returned correctly. - """ - actual = get_base_url(source_url) - assert actual == expected - - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.utils.get_catalog_api_client') - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.utils.get_catalog_api_base_url') - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.utils.get_api_data') - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.utils.check_catalog_integration_and_get_user') - def test_get_top_skill_categories_for_job( - self, - mock_check_catalog_integration_and_get_user, - mock_get_api_data, - mock_get_catalog_api_base_url, - mock_get_catalog_api_client - ): - """ - Test that get_top_skill_categories_for_job returns jobs categories. - """ - mock_check_catalog_integration_and_get_user.return_value = self.service_user, self.catalog_integration - mock_get_api_data.return_value = DUMMY_CATEGORIES_RESPONSE - mock_get_catalog_api_base_url.return_value = 'localhost:18381/api' - mock_get_catalog_api_client.return_value = self.client - result = get_top_skill_categories_for_job(1) - assert result == DUMMY_CATEGORIES_RESPONSE - - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.utils.get_catalog_api_client') - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.utils.get_catalog_api_base_url') - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.utils.get_api_data') - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.utils.check_catalog_integration_and_get_user') - def test_get_job_holder_usernames( - self, - mock_check_catalog_integration_and_get_user, - mock_get_api_data, - mock_get_catalog_api_base_url, - mock_get_catalog_api_client - ): - """ - Test that test_get_job_holder_usernames returns usernames. - """ - mock_check_catalog_integration_and_get_user.return_value = self.service_user, self.catalog_integration - mock_get_api_data.return_value = DUMMY_USERNAMES_RESPONSE - mock_get_catalog_api_base_url.return_value = 'localhost:18381/api' - mock_get_catalog_api_client.return_value = self.client - result = get_job_holder_usernames(1) - assert result == DUMMY_USERNAMES_RESPONSE diff --git a/openedx/core/djangoapps/user_api/learner_skill_levels/tests/test_views.py b/openedx/core/djangoapps/user_api/learner_skill_levels/tests/test_views.py deleted file mode 100644 index 369f32787f90..000000000000 --- a/openedx/core/djangoapps/user_api/learner_skill_levels/tests/test_views.py +++ /dev/null @@ -1,119 +0,0 @@ -""" -Test cases for LearnerSkillLevelsView. -""" - -from unittest import mock - -from django.urls import reverse -from rest_framework.test import APIClient, APITestCase - -from common.djangoapps.student.tests.factories import TEST_PASSWORD, UserFactory - -from .testutils import DUMMY_CATEGORIES_RESPONSE, DUMMY_USERNAMES_RESPONSE - - -class LearnerSkillLevelsViewTests(APITestCase): - """ - The tests for LearnerSkillLevelsView. - """ - - def setUp(self): - super().setUp() - - self.client = APIClient() - self.user = UserFactory.create(password=TEST_PASSWORD) - self.url = reverse('learner_skill_level', kwargs={'job_id': '1'}) - - for username in DUMMY_USERNAMES_RESPONSE['usernames']: - UserFactory(username=username) - - def test_unauthorized_get_endpoint(self): - """ - Test that endpoint is only accessible to authorized user. - """ - response = self.client.get(self.url) - assert response.status_code == 401 - - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.views.get_top_skill_categories_for_job') - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.views.get_job_holder_usernames') - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.api.generate_skill_score_mapping') - def test_get_endpoint( - self, - mock_generate_skill_score_mapping, - mock_get_job_holder_usernames, - mock_get_top_skill_categories_for_job - ): - """ - Test that response if returned with correct scores appended. - """ - mock_get_top_skill_categories_for_job.return_value = DUMMY_CATEGORIES_RESPONSE - mock_get_job_holder_usernames.return_value = DUMMY_USERNAMES_RESPONSE - mock_generate_skill_score_mapping.return_value = {'Technology Roadmap': 2, 'Python': 3} - - self.client.login(username=self.user.username, password=TEST_PASSWORD) - response = self.client.get(self.url) - assert response.status_code == 200 - # check if the response is mutated and scores are appended for skills - # for when some skills are learned by user in a category, check if user_score and avg score is appended - assert response.data['skill_categories'][0]['user_score'] == 0.8 - assert response.data['skill_categories'][0]['edx_average_score'] == 0.8 - - # for when no skill is learned by user in a category, check if user_score and avg score is appended - assert response.data['skill_categories'][1]['user_score'] == 0.0 - assert response.data['skill_categories'][1]['edx_average_score'] == 0.0 - - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.views.get_top_skill_categories_for_job') - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.views.get_job_holder_usernames') - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.api.generate_skill_score_mapping') - def test_get_with_less_than_5_users( - self, - mock_generate_skill_score_mapping, - mock_get_job_holder_usernames, - mock_get_top_skill_categories_for_job - ): - """ - Test that average value is None when users are less than 5. - """ - mock_get_top_skill_categories_for_job.return_value = DUMMY_CATEGORIES_RESPONSE - mock_get_job_holder_usernames.return_value = {"usernames": ['user1', 'user2']} - mock_generate_skill_score_mapping.return_value = {'Technology Roadmap': 2, 'Python': 3} - - self.client.login(username=self.user.username, password=TEST_PASSWORD) - response = self.client.get(self.url) - assert response.status_code == 200 - # check if the response is mutated and scores are appended for skills - # for when some skills are learned by user in a category, check if user_score and avg score is appended - assert response.data['skill_categories'][0]['user_score'] == 0.8 - assert response.data['skill_categories'][0]['edx_average_score'] is None - - # for when no skill is learned by user in a category, check if user_score and avg score is appended - assert response.data['skill_categories'][1]['user_score'] == 0.0 - assert response.data['skill_categories'][1]['edx_average_score'] is None - - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.views.get_top_skill_categories_for_job') - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.views.get_job_holder_usernames') - @mock.patch('openedx.core.djangoapps.user_api.learner_skill_levels.api.generate_skill_score_mapping') - def test_get_no_skills_learned( - self, - mock_generate_skill_score_mapping, - mock_get_job_holder_usernames, - mock_get_top_skill_categories_for_job - ): - """ - Test that score is 0.0 when no skills are learned by a user. - """ - mock_get_top_skill_categories_for_job.return_value = DUMMY_CATEGORIES_RESPONSE - mock_get_job_holder_usernames.return_value = DUMMY_USERNAMES_RESPONSE - mock_generate_skill_score_mapping.return_value = {} - - self.client.login(username=self.user.username, password=TEST_PASSWORD) - response = self.client.get(self.url) - assert response.status_code == 200 - # check if the response is mutated and scores are appended for skills - # for when some skills are learned by user in a category, check if user_score and avg score is appended - assert response.data['skill_categories'][0]['user_score'] == 0.0 - assert response.data['skill_categories'][0]['edx_average_score'] == 0.0 - - # for when no skill is learned by user in a category, check if user_score and avg score is appended - assert response.data['skill_categories'][1]['user_score'] == 0.0 - assert response.data['skill_categories'][1]['edx_average_score'] == 0.0 diff --git a/openedx/core/djangoapps/user_api/learner_skill_levels/tests/testutils.py b/openedx/core/djangoapps/user_api/learner_skill_levels/tests/testutils.py deleted file mode 100644 index 9be19ed15a20..000000000000 --- a/openedx/core/djangoapps/user_api/learner_skill_levels/tests/testutils.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -Utilities for unit tests of learner skill levels. -""" - -DUMMY_CATEGORIES_RESPONSE = { - "job": "Digital Product Manager", - "skill_categories": [ - { - "name": "Information Technology", - "id": 1, - "skills": [ - {"id": 3, "name": "Technology Roadmap"}, - {"id": 12, "name": "Python"}, - {"id": 2, "name": "MongoDB"} - ], - "skills_subcategories": [ - { - "id": 1, - "name": "Databases", - "skills": [ - {"id": 1, "name": "Query Languages"}, - {"id": 2, "name": "MongoDB"}, - ] - }, - { - "id": 2, - "name": "IT Management", - "skills": [ - {"id": 3, "name": "Technology Roadmap"}, - ] - }, - ] - }, - { - "name": "Finance", - "id": 2, - "skills": [ - {"id": 4, "name": "Accounting"}, - {"id": 5, "name": "TQM"}, - ], - "skills_subcategories": [ - { - "id": 3, - "name": "Auditing", - "skills": [ - {"id": 4, "name": "Accounting"}, - {"id": 5, "name": "TQM"}, - ] - }, - { - "id": 4, - "name": "Management", - "skills": [ - {"id": 6, "name": "Financial Management"}, - ] - }, - ] - }, - ] -} - -DUMMY_CATEGORIES_WITH_SCORES = { - "job": "Digital Product Manager", - "skill_categories": [ - { - "name": "Information Technology", - "id": 1, - "skills": [ - {"id": 3, "name": "Technology Roadmap", "score": 1}, - {"id": 12, "name": "Python", "score": 2}, - {"id": 2, "name": "MongoDB", "score": 3} - ], - "user_score": 0.8, - }, - { - "name": "Finance", - "id": 2, - "skills": [ - {"id": 1, "name": "Query Languages", "score": 1}, - {"id": 4, "name": "System Design", "score": 2}, - ], - "user_score": 0.3, - }, - ] -} - -DUMMY_USER_SCORES_MAP = { - "Information Technology": [0.1, 0.3, 0.5, 0.7], - "Finance": [0.2, 0.4, 0.6, 0.8] - -} - -DUMMY_USERNAMES_RESPONSE = { - "usernames": [ - 'test_user_1', - 'test_user_2', - 'test_user_3', - 'test_user_4', - 'test_user_5', - 'test_user_6', - ] -} - -DUMMY_COURSE_DATA_RESPONSE = { - "key": "AWS+OTP", - "uuid": "fe1a9ad4-a452-45cd-80e5-9babd3d43f96", - "title": "Demonstration Course", - "level_type": 'Advanced', - "skill_names": ["python", "MongoDB", "Data Science"] -} diff --git a/openedx/core/djangoapps/user_api/learner_skill_levels/utils.py b/openedx/core/djangoapps/user_api/learner_skill_levels/utils.py deleted file mode 100644 index 22f8745e0828..000000000000 --- a/openedx/core/djangoapps/user_api/learner_skill_levels/utils.py +++ /dev/null @@ -1,182 +0,0 @@ -""" -Utilities for learner_skill_levels. -""" -from logging import getLogger -from urllib.parse import urlparse - -from lms.djangoapps.grades.models import PersistentCourseGrade # lint-amnesty, pylint: disable=unused-import -from openedx.core.djangoapps.catalog.utils import ( - get_catalog_api_client, - check_catalog_integration_and_get_user, - get_catalog_api_base_url, - -) -from openedx.core.djangoapps.catalog.utils import get_course_data, get_course_run_data -from openedx.core.lib.edx_api_utils import get_api_data - -from .constants import LEVEL_TYPE_SCORE_MAPPING - -LOGGER = getLogger(__name__) # pylint: disable=invalid-name - - -def get_course_run_ids(user): - """ - Returns all the course run ids of the courses that user has passed from PersistentCourseGrade model. - """ - return list( - PersistentCourseGrade.objects.filter( - user_id=user.id, - passed_timestamp__isnull=False - ).values_list('course_id', flat=True) - ) - - -def generate_skill_score_mapping(user): - """ - Generates a skill to score mapping for all the skills user has learner so far in passed courses. - """ - # get course_run_ids of all courses the user has passed - course_run_ids = get_course_run_ids(user) - - skill_score_mapping = {} - for course_run_id in course_run_ids: - # fetch course details from course run id to get course key - course_run_data = get_course_run_data(course_run_id, ['course']) - if course_run_data: - # fetch course details to get level type and skills - course_data = get_course_data(course_run_data['course'], ['skill_names', 'level_type']) - skill_names = course_data['skill_names'] - level_type = course_data['level_type'] - - # if a level_type is None for a course, we should skip that course. - if level_type: - score = LEVEL_TYPE_SCORE_MAPPING[level_type.capitalize()] - for skill in skill_names: - if skill in skill_score_mapping: - # assign scores b/w 1-3 based on level type - # assign the larger score if skill is repeated in 2 courses - skill_score_mapping[skill] = max(score, skill_score_mapping[skill]) - else: - skill_score_mapping.update({skill: score}) - LOGGER.info( - "Could not find course_key for course run id [%s].", course_run_id - ) - return skill_score_mapping - - -def calculate_user_skill_score(skills_with_score): - """ - Calculates user skill score to see where the user falls in a certain job category. - """ - # generate a dict with skill name as key and score as value - # take only those skills that user has learned. - - if not skills_with_score: - return 0.0 - - skills_score_dict = { - item['name']: item['score'] - for item in skills_with_score - if item['score'] is not None - } - sum_of_skills = sum(skills_score_dict.values()) - skills_count = len(skills_score_dict) - if not skills_count: - return 0.0 - # sum of skills score in the category/ 3*no. of skills in category - return round(sum_of_skills / (3 * skills_count), 1) - - -def get_skills_score(skills, learner_skill_score): - """ - Takes each skill item in list and appends its score to it. - For a skill that doesn't exist in learner's skills set, appends None as score. - """ - for skill in skills: - skill['score'] = learner_skill_score.get(skill['name']) - - -def update_category_user_scores_map(categories, category_user_scores_map): - """ - Appends user's scores for each category in the dict. - """ - for category in categories: - category_user_scores_map[category['name']].append(category['user_score']) - - -def update_edx_average_score(categories, user_score_mapping): - """ - Calculates average score for each category and appends it. - """ - for category in categories: - category_scores = user_score_mapping[category['name']] - sum_score = sum(category_scores, 0.0) - average_score = round(sum_score / len(category_scores), 1) - category['edx_average_score'] = average_score - - -def get_base_url(url): - """ - Returns the base url for any given url. - """ - if url: - parsed = urlparse(url) - return f'{parsed.scheme}://{parsed.netloc}' - - -def get_top_skill_categories_for_job(job_id): - """ - Retrieve top categories for the job with the given job_id. - - Arguments: - job_id (int): id of the job about which we are retrieving information. - - Returns: - dict with top 5 categories of specified job. - """ - user, catalog_integration = check_catalog_integration_and_get_user(error_message_field='Skill Categories') - if user: - api_client = get_catalog_api_client(user) - root_url = get_catalog_api_base_url() - base_api_url = get_base_url(root_url) - resource = '/taxonomy/api/v1/job-top-subcategories' - cache_key = f'{catalog_integration.CACHE_KEY}.job-categories.{job_id}' - data = get_api_data( - catalog_integration, - resource=resource, - resource_id=job_id, - api_client=api_client, - base_api_url=base_api_url, - cache_key=cache_key if catalog_integration.is_cache_enabled else None, - ) - if data: - return data - - -def get_job_holder_usernames(job_id): - """ - Retrieve usernames of users who have the same job as given job_id. - - Arguments: - job_id (int): id of the job for which we are retrieving usernames. - - Returns: - list with oldest 100 users' usernames that exist in our system. - """ - user, catalog_integration = check_catalog_integration_and_get_user(error_message_field='Job Holder Usernames') - if user: - api_client = get_catalog_api_client(user) - root_url = get_catalog_api_base_url() - base_api_url = get_base_url(root_url) - resource = '/taxonomy/api/v1/job-holder-usernames' - cache_key = f'{catalog_integration.CACHE_KEY}.job-holder-usernames.{job_id}' - data = get_api_data( - catalog_integration, - resource=resource, - resource_id=job_id, - api_client=api_client, - base_api_url=base_api_url, - cache_key=cache_key if catalog_integration.is_cache_enabled else None, - ) - if data: - return data diff --git a/openedx/core/djangoapps/user_api/learner_skill_levels/views.py b/openedx/core/djangoapps/user_api/learner_skill_levels/views.py deleted file mode 100644 index 3fee78588bd4..000000000000 --- a/openedx/core/djangoapps/user_api/learner_skill_levels/views.py +++ /dev/null @@ -1,129 +0,0 @@ -""" -Views for learner_skill_levels. -""" -from collections import defaultdict -from copy import deepcopy - -from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user -from rest_framework import permissions, status -from rest_framework.response import Response -from rest_framework.views import APIView - -from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication -from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser -from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser - -from .api import get_learner_skill_levels -from .utils import get_top_skill_categories_for_job, get_job_holder_usernames, update_category_user_scores_map, \ - update_edx_average_score - - -class LearnerSkillLevelsView(APIView): - """ - **Use Cases** - - Returns top 5 job categories for the given job. Checks which skill the user has learned via courses - and assign scores to each skill in category. Also takes first 100 users in our system to calculate - average score for each category. - - **Request format** - - GET /api/user/v1/skill_level/{job_id}/ - - **Response Values for GET** - - If the specified job_id doesn't exist, an HTTP - 404 "Not Found" response is returned. - - If a logged in user makes a request with an existing job, an HTTP 200 - "OK" response is returned that contains a JSON string. - - **Example Request** - - GET /api/user/v1/skill_level/1/ - - **Example Response** - - { - "job": "Digital Product Manager", - "skill_categories": [ - { - "name": "Information Technology", - "id": 1, - "skills": [ - {"id": 2, "name": "Query Languages", "score": 1}, - {"id": 3, "name": "MongoDB", "score": 3}, - ] - "user_score": 0.4, // request user's score - "edx_average_score": 0.7, - "skills_subcategories": [ - { - "id": 1, - "name": "Databases", - "skills": [ - {"id": 2, "name": "Query Languages", "score": 1}, - {"id": 3, "name": "MongoDB", "score": None}, - ] - }, - { - "id": 2, - "name": "IT Management", - "skills": [ - {"id": 1, "name": "Technology Roadmap", "score": 2}, - ] - }, - // here remaining job related skills subcategories - ] - }, - - // Here more 4 skill categories - ] - } - """ - authentication_classes = ( - JwtAuthentication, - BearerAuthenticationAllowInactiveUser, - SessionAuthenticationAllowInactiveUser - ) - permission_classes = (permissions.IsAuthenticated, ) - - def get(self, request, job_id): - """ - GET /api/user/v1/skill_level/{job_id}/ - """ - # get top categories for the given job - job_skill_categories = get_top_skill_categories_for_job(job_id) - if not job_skill_categories: - return Response( - status=status.HTTP_404_NOT_FOUND, - data={'message': "The job id doesn't exist, enter a valid job id."} - ) - - # assign scores for every skill request user has learned - top_categories = deepcopy(job_skill_categories['skill_categories']) - user_category_scores = get_learner_skill_levels( - user=request.user, - top_categories=top_categories, - ) - - # repeat the same logic for 100 job holder users in our system - job_holder_usernames = get_job_holder_usernames(job_id) - users = User.objects.filter(username__in=job_holder_usernames['usernames']) - - # edx_avg_score should only be calculated if users count is greater than 5, else skip it. - if len(users) > 5: - # To save all the users' scores against every category to calculate average score - category_user_scores_map = defaultdict(list) - - for user in users: - categories = deepcopy(job_skill_categories['skill_categories']) - categories_with_scores = get_learner_skill_levels( - user=user, - top_categories=categories, - ) - update_category_user_scores_map(categories_with_scores, category_user_scores_map) - - update_edx_average_score(user_category_scores, category_user_scores_map) - - job_skill_categories['skill_categories'] = user_category_scores - return Response(job_skill_categories) diff --git a/openedx/core/djangoapps/user_api/models.py b/openedx/core/djangoapps/user_api/models.py index 6c1dd832a38d..d776dac8fe2a 100644 --- a/openedx/core/djangoapps/user_api/models.py +++ b/openedx/core/djangoapps/user_api/models.py @@ -426,6 +426,8 @@ def __str__(self): class BulkUserRetirementConfig(ConfigurationModel): """ Configuration to store a csv file that will be used in retire_user management command. + + .. no_pii: """ # Timeout set to 0 so that the model does not read from cached config in case the config entry is deleted. cache_timeout = 0 diff --git a/openedx/core/djangoapps/user_api/urls.py b/openedx/core/djangoapps/user_api/urls.py index 6bd34fccc6e5..b4bf1b3b2bbe 100644 --- a/openedx/core/djangoapps/user_api/urls.py +++ b/openedx/core/djangoapps/user_api/urls.py @@ -19,7 +19,6 @@ NameChangeView, UsernameReplacementView, CancelAccountRetirementStatusView ) -from .learner_skill_levels.views import LearnerSkillLevelsView from . import views as user_api_views from .models import UserPreference from .preferences.views import PreferencesDetailView, PreferencesView @@ -191,11 +190,6 @@ PreferencesDetailView.as_view(), name='preferences_detail_api' ), - re_path( - r'^v1/skill_level/(?P[0-9]+)/$', - LearnerSkillLevelsView.as_view(), - name="learner_skill_level" - ), # Moved from user_api/legacy_urls.py path('v1/', include(USER_API_ROUTER.urls)), diff --git a/openedx/core/djangoapps/user_authn/views/registration_form.py b/openedx/core/djangoapps/user_authn/views/registration_form.py index 7a0207f8b93c..978d303c9a1f 100644 --- a/openedx/core/djangoapps/user_authn/views/registration_form.py +++ b/openedx/core/djangoapps/user_authn/views/registration_form.py @@ -148,6 +148,7 @@ class AccountCreationForm(forms.Form): _EMAIL_INVALID_MSG = _("A properly formatted e-mail is required") _NAME_TOO_SHORT_MSG = _("Your legal name must be a minimum of one character long") + _NAME_TOO_LONG_MSG = _("Your legal name is too long. It must not exceed %(max_length)s characters") # TODO: Resolve repetition @@ -167,9 +168,11 @@ class AccountCreationForm(forms.Form): name = forms.CharField( min_length=accounts.NAME_MIN_LENGTH, + max_length=accounts.NAME_MAX_LENGTH, error_messages={ "required": _NAME_TOO_SHORT_MSG, "min_length": _NAME_TOO_SHORT_MSG, + "max_length": _NAME_TOO_LONG_MSG % {"max_length": accounts.NAME_MAX_LENGTH}, }, validators=[validate_name] ) diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_register.py b/openedx/core/djangoapps/user_authn/views/tests/test_register.py index 16f7da8010d6..77b5d074fba2 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_register.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_register.py @@ -316,6 +316,30 @@ def test_register_fullname_url_validation_error(self): } ) + def test_register_fullname_max_lenghth_validation_error(self): + """ + Full name error detection test if the length exceeds 255 characters. + """ + expected_error_message = f"Your legal name is too long. It must not exceed {NAME_MAX_LENGTH} characters" + + response = self.client.post(self.url, { + "email": self.EMAIL, + "name": "x" * 256, + "username": self.USERNAME, + "password": self.PASSWORD, + "honor_code": "true", + }) + assert response.status_code == 400 + + response_json = json.loads(response.content.decode('utf-8')) + self.assertDictEqual( + response_json, + { + "name": [{"user_message": expected_error_message}], + "error_code": "validation-error" + } + ) + def test_register_fullname_html_validation_error(self): """ Test for catching invalid full name errors diff --git a/openedx/core/djangoapps/video_config/models.py b/openedx/core/djangoapps/video_config/models.py index 9ea7f8f44d2d..9a54585475f2 100644 --- a/openedx/core/djangoapps/video_config/models.py +++ b/openedx/core/djangoapps/video_config/models.py @@ -98,7 +98,8 @@ class CourseYoutubeBlockedFlag(ConfigurationModel): Disables the playback of youtube videos for a given course. If the flag is present for the course, and set to "enabled", then youtube is disabled for that course. - .. no_pii + + .. no_pii: """ KEY_FIELDS = ('course_id',) diff --git a/openedx/core/lib/cache_utils.py b/openedx/core/lib/cache_utils.py index c379ab2d961a..a889da6130ee 100644 --- a/openedx/core/lib/cache_utils.py +++ b/openedx/core/lib/cache_utils.py @@ -207,7 +207,7 @@ def decorator(*args, **kwargs): # pylint: disable=unused-argument,missing-docst def zpickle(data): """Given any data structure, returns a zlib compressed pickled serialization.""" - return zlib.compress(pickle.dumps(data, 4)) # Keep this constant as we upgrade from python 2 to 3. + return zlib.compress(pickle.dumps(data, 4)) def zunpickle(zdata): diff --git a/openedx/core/lib/celery/__init__.py b/openedx/core/lib/celery/__init__.py index 855970c1df3e..a667b467806f 100644 --- a/openedx/core/lib/celery/__init__.py +++ b/openedx/core/lib/celery/__init__.py @@ -23,7 +23,6 @@ # lms.celery. See module docstring! APP = Celery('proj') -APP.conf.task_protocol = 1 # Using a string here means the worker will not have to # pickle the object when using Windows. APP.config_from_object('django.conf:settings') diff --git a/openedx/core/lib/grade_utils.py b/openedx/core/lib/grade_utils.py index 2e7073e962dc..af3d12c9e2fd 100644 --- a/openedx/core/lib/grade_utils.py +++ b/openedx/core/lib/grade_utils.py @@ -48,11 +48,9 @@ def is_score_higher_or_equal(earned1, possible1, earned2, possible2, treat_undef def round_away_from_zero(number, digits=0): """ Round numbers using the 'away from zero' strategy as opposed to the - 'Banker's rounding strategy.' The strategy refers to how we round when - a number is half way between two numbers. eg. 0.5, 1.5, etc. In python 2 - positive numbers in this category would be rounded up and negative numbers - would be rounded down. ie. away from zero. In python 3 numbers round - towards even. So 0.5 would round to 0 but 1.5 would round to 2. + 'Banker's rounding strategy.' The strategy refers to how we round when + a number is half way between two numbers. eg. 0.5, 1.5, etc. In python 3 + numbers round towards even. So 0.5 would round to 0 but 1.5 would round to 2. See here for more on floating point rounding strategies: https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules diff --git a/openedx/features/announcements/models.py b/openedx/features/announcements/models.py index cb202ce521ba..f58f61165db6 100644 --- a/openedx/features/announcements/models.py +++ b/openedx/features/announcements/models.py @@ -7,7 +7,11 @@ class Announcement(models.Model): - """Site-wide announcements to be displayed on the dashboard""" + """ + Site-wide announcements to be displayed on the dashboard + + .. no_pii: + """ class Meta: app_label = 'announcements' diff --git a/openedx/features/course_experience/url_helpers.py b/openedx/features/course_experience/url_helpers.py index f62e879f0994..e1fe4110c613 100644 --- a/openedx/features/course_experience/url_helpers.py +++ b/openedx/features/course_experience/url_helpers.py @@ -15,6 +15,7 @@ from six.moves.urllib.parse import urlencode, urlparse from lms.djangoapps.courseware.toggles import courseware_mfe_is_active +from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order from xmodule.modulestore.search import navigation_index, path_to_location # lint-amnesty, pylint: disable=wrong-import-order @@ -24,6 +25,7 @@ def get_courseware_url( usage_key: UsageKey, request: Optional[HttpRequest] = None, + is_staff: bool = False, ) -> str: """ Return the URL to the canonical learning experience for a given block. @@ -44,12 +46,13 @@ def get_courseware_url( get_url_fn = _get_new_courseware_url else: get_url_fn = _get_legacy_courseware_url - return get_url_fn(usage_key=usage_key, request=request) + return get_url_fn(usage_key=usage_key, request=request, is_staff=is_staff) def _get_legacy_courseware_url( usage_key: UsageKey, request: Optional[HttpRequest] = None, + is_staff: bool = None ) -> str: """ Return the URL to Legacy (LMS-rendered) courseware content. @@ -90,6 +93,7 @@ def _get_legacy_courseware_url( def _get_new_courseware_url( usage_key: UsageKey, request: Optional[HttpRequest] = None, + is_staff: bool = None, ) -> str: """ Return the URL to the "new" (Learning Micro-Frontend) experience for a given block. @@ -99,7 +103,13 @@ def _get_new_courseware_url( * NoPathToItem if we cannot build a path to the `usage_key`. """ course_key = usage_key.course_key.replace(version_guid=None, branch=None) - path = path_to_location(modulestore(), usage_key, request, full_path=True) + preview = request.GET.get('preview') if request and request.GET else False + branch_type = ( + ModuleStoreEnum.Branch.draft_preferred + ) if preview and is_staff else ModuleStoreEnum.Branch.published_only + + path = path_to_location(modulestore(), usage_key, request, full_path=True, branch_type=branch_type) + if len(path) <= 1: # Course-run-level block: # We have no Sequence or Unit to return. @@ -120,6 +130,7 @@ def _get_new_courseware_url( course_key=course_key, sequence_key=sequence_key, unit_key=unit_key, + preview=preview, params=request.GET if request and request.GET else None, ) @@ -129,6 +140,7 @@ def make_learning_mfe_courseware_url( sequence_key: Optional[UsageKey] = None, unit_key: Optional[UsageKey] = None, params: Optional[QueryDict] = None, + preview: bool = None, ) -> str: """ Return a str with the URL for the specified courseware content in the Learning MFE. @@ -160,6 +172,16 @@ def make_learning_mfe_courseware_url( `params` is an optional QueryDict object (e.g. request.GET) """ mfe_link = f'{settings.LEARNING_MICROFRONTEND_URL}/course/{course_key}' + get_params = params.copy() if params else None + + if preview: + if len(get_params.keys()) > 1: + get_params.pop('preview') + else: + get_params = None + + if (unit_key or sequence_key): + mfe_link = f'{settings.LEARNING_MICROFRONTEND_URL}/preview/course/{course_key}' if sequence_key: mfe_link += f'/{sequence_key}' @@ -167,8 +189,8 @@ def make_learning_mfe_courseware_url( if unit_key: mfe_link += f'/{unit_key}' - if params: - mfe_link += f'?{params.urlencode()}' + if get_params: + mfe_link += f'?{get_params.urlencode()}' return mfe_link diff --git a/openedx/features/discounts/models.py b/openedx/features/discounts/models.py index bca6ef7b37c1..8c7c5ff53ea0 100644 --- a/openedx/features/discounts/models.py +++ b/openedx/features/discounts/models.py @@ -15,6 +15,8 @@ class DiscountRestrictionConfig(StackedConfigurationModel): """ A ConfigurationModel used to manage restrictons for lms-controlled discounts + + .. no_pii: """ STACKABLE_FIELDS = ('disabled',) @@ -43,6 +45,8 @@ def __str__(self): class DiscountPercentageConfig(StackedConfigurationModel): """ A ConfigurationModel to configure the discount percentage for the first purchase discount + + .. no_pii: """ STACKABLE_FIELDS = ('percentage',) percentage = models.PositiveIntegerField() diff --git a/package-lock.json b/package-lock.json index 82a6611d3abb..5347cda11cf2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,11 @@ "version": "0.1.0", "hasInstallScript": true, "dependencies": { - "@babel/core": "7.25.2", + "@babel/core": "7.26.0", "@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.24.7", + "@babel/preset-react": "7.25.9", "@edx/brand-edx.org": "^2.0.7", "@edx/edx-bootstrap": "1.0.4", "@edx/edx-proctoring": "^4.18.1", @@ -28,69 +28,69 @@ "backbone.paginator": "2.0.8", "bootstrap": "4.0.0", "camelize": "1.0.1", - "classnames": "2.3.1", + "classnames": "2.5.1", "css-loader": "0.28.8", "datatables": "1.10.18", - "datatables.net-fixedcolumns": "3.2.6", + "datatables.net-fixedcolumns": "5.0.4", "edx-proctoring-proctortrack": "git+https://git@github.com/anupdhabarde/edx-proctoring-proctortrack.git#f0fa9edbd16aa5af5a41ac309d2609e529ea8732", - "edx-ui-toolkit": "1.5.5", + "edx-ui-toolkit": "1.8.6", "exports-loader": "0.6.4", "file-loader": "^6.2.0", "font-awesome": "4.7.0", - "hls.js": "0.14.17", - "imports-loader": "0.7.1", + "hls.js": "1.5.17", + "imports-loader": "0.8.0", "jest-environment-jsdom": "^26.0.0", "jquery": "2.2.4", "jquery-migrate": "1.4.1", "jquery.scrollto": "2.1.3", "js-cookie": "3.0.5", "moment": "2.30.1", - "moment-timezone": "0.5.45", - "node-gyp": "10.0.1", + "moment-timezone": "0.5.46", + "node-gyp": "10.2.0", "picturefill": "3.0.3", "popper.js": "1.16.1", - "prop-types": "15.6.0", - "raw-loader": "0.5.1", + "prop-types": "15.8.1", + "raw-loader": "4.0.2", "react": "16.14.0", "react-dom": "16.14.0", "react-focus-lock": "^1.19.1", - "react-redux": "5.0.7", + "react-redux": "5.1.2", "react-router-dom": "5.1.2", - "react-slick": "0.29.0", + "react-slick": "0.30.2", "redux": "3.7.2", "redux-thunk": "2.2.0", "requirejs": "2.3.7", - "rtlcss": "2.6.2", + "rtlcss": "4.3.0", "sass": "^1.54.8", - "sass-loader": "^14.1.1", + "sass-loader": "^16.0.0", "scriptjs": "2.5.9", - "style-loader": "0.18.2", + "style-loader": "4.0.0", "svg-inline-loader": "0.8.2", - "uglify-js": "2.7.0", - "underscore": "1.13.1", + "uglify-js": "3.19.3", + "underscore": "1.13.7", "underscore.string": "3.3.6", "webpack": "^5.90.3", "webpack-bundle-tracker": "0.4.3", - "webpack-merge": "4.1.1", + "webpack-merge": "4.2.2", "whatwg-fetch": "2.0.4", "which-country": "1.0.0" }, "devDependencies": { - "@edx/eslint-config": "^3.1.1", - "@edx/mockprock": "github:openedx/mockprock#3ad18c6888e6521e9bf7a4df0db6f8579b928235", + "@edx/eslint-config": "^4.0.0", + "@edx/mockprock": "github:openedx/mockprock#d70b05231bd46b0122616c24e209c890ef2633c0", "@edx/stylelint-config-edx": "2.3.3", "babel-jest": "26.6.3", "enzyme": "3.11.0", "enzyme-adapter-react-16": "1.15.8", - "eslint-import-resolver-webpack": "0.13.8", + "eslint-import-resolver-webpack": "0.13.9", "jasmine-core": "2.6.4", "jasmine-jquery": "git+https://git@github.com/velesin/jasmine-jquery.git#ebad463d592d3fea00c69f26ea18a930e09c7b58", "jest": "26.6.3", - "jest-enzyme": "6.1.2", + "jest-enzyme": "7.1.2", "karma": "0.13.22", "karma-chrome-launcher": "0.2.3", "karma-coverage": "0.5.5", - "karma-firefox-launcher": "0.1.7", + "karma-firefox-launcher": "2.1.3", "karma-jasmine": "0.3.8", "karma-jasmine-html-reporter": "0.2.2", "karma-junit-reporter": "1.2.0", @@ -101,11 +101,11 @@ "karma-webpack": "^5.0.1", "plato": "1.7.0", "react-test-renderer": "16.14.0", - "selenium-webdriver": "3.6.0", + "selenium-webdriver": "4.26.0", "sinon": "2.4.1", "squirejs": "0.1.0", "string-replace-loader": "^3.1.0", - "stylelint-formatter-pretty": "1.1.4", + "stylelint-formatter-pretty": "4.0.1", "webpack-cli": "^5.1.4" } }, @@ -132,12 +132,13 @@ } }, "node_modules/@babel/code-frame": { - "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==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" }, "engines": { @@ -145,30 +146,30 @@ } }, "node_modules/@babel/compat-data": { - "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==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", + "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "license": "MIT", "dependencies": { "@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", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -190,54 +191,55 @@ "license": "MIT" }, "node_modules/@babel/generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", - "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.0", + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz", + "integrity": "sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -246,17 +248,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", + "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", "license": "MIT", "dependencies": { - "@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", + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.25.9", "semver": "^6.3.1" }, "engines": { @@ -267,13 +269,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz", + "integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "regexpu-core": "^5.3.1", + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.1.1", "semver": "^6.3.1" }, "engines": { @@ -284,9 +286,10 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz", - "integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", + "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", + "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", @@ -299,41 +302,40 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.8" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "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==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "license": "MIT", "dependencies": { - "@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" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -343,35 +345,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-wrap-function": "^7.25.0", - "@babel/traverse": "^7.25.0" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -381,14 +383,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", + "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/traverse": "^7.25.0" + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -398,107 +400,92 @@ } }, "node_modules/@babel/helper-simple-access": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", + "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", "license": "MIT", "dependencies": { - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "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.25.0", - "@babel/types": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", - "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.2" + "@babel/types": "^7.26.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -508,13 +495,13 @@ } }, "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.3" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -524,12 +511,12 @@ } }, "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -539,12 +526,12 @@ } }, "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==", + "version": "7.25.9", + "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.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -554,14 +541,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -571,13 +558,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -620,6 +607,7 @@ "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -643,6 +631,7 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -650,49 +639,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-import-assertions": { - "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==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -702,12 +655,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "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==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -720,6 +673,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -731,6 +685,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -739,12 +694,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -757,6 +712,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -768,6 +724,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -779,6 +736,7 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -801,6 +759,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -812,6 +771,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -819,24 +779,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -863,12 +810,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -878,15 +825,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", + "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", "license": "MIT", "dependencies": { - "@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" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -896,14 +842,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-remap-async-to-generator": "^7.24.7" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -913,12 +859,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", + "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -928,12 +874,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -943,13 +889,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -959,14 +905,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "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==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", "license": "MIT", "dependencies": { - "@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" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -976,16 +921,16 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", "license": "MIT", "dependencies": { - "@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", + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", "globals": "^11.1.0" }, "engines": { @@ -996,13 +941,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/template": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1012,12 +957,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1027,13 +972,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1043,12 +988,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1058,13 +1003,13 @@ } }, "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1074,13 +1019,12 @@ } }, "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1090,13 +1034,13 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz", + "integrity": "sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==", "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1106,13 +1050,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1122,13 +1065,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1138,14 +1081,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/traverse": "^7.25.1" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1155,13 +1098,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1171,12 +1113,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1186,13 +1128,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1202,12 +1143,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1217,13 +1158,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1233,14 +1174,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", + "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.8", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-simple-access": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-simple-access": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1250,15 +1191,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", "license": "MIT", "dependencies": { - "@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" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1268,13 +1209,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1284,13 +1225,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1300,12 +1241,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1315,13 +1256,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", + "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1331,13 +1271,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1347,12 +1286,12 @@ } }, "node_modules/@babel/plugin-transform-object-assign": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.25.9.tgz", + "integrity": "sha512-I/Vl1aQnPsrrn837oLbo+VQtkNcjuuiATqwmuweg4fTauwHHQoxyjmjjOVKyO8OaTxgqYTKW3LuQsykXjDf5Ag==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1362,15 +1301,14 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", "license": "MIT", "dependencies": { - "@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.7" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1380,13 +1318,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1396,13 +1334,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1412,14 +1349,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1429,12 +1365,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", - "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1444,13 +1380,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1460,15 +1396,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", "license": "MIT", "dependencies": { - "@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" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1478,12 +1413,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1493,12 +1428,12 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", + "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1508,16 +1443,16 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", "license": "MIT", "dependencies": { - "@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" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1527,12 +1462,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", + "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", "license": "MIT", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.24.7" + "@babel/plugin-transform-react-jsx": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1542,13 +1477,13 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", + "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1558,12 +1493,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-plugin-utils": "^7.25.9", "regenerator-transform": "^0.15.2" }, "engines": { @@ -1573,13 +1508,29 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-reserved-words": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1589,12 +1540,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1604,13 +1555,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", - "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1620,12 +1571,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1635,12 +1586,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1650,12 +1601,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", + "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1665,12 +1616,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1680,13 +1631,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1696,13 +1647,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1712,13 +1663,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1738,93 +1689,79 @@ } }, "node_modules/@babel/preset-env": { - "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.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", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", + "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", "@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.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", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@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/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.25.9", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.25.9", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.25.9", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.25.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.25.9", + "@babel/plugin-transform-typeof-symbol": "^7.25.9", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", "@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-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.37.1", + "core-js-compat": "^3.38.1", "semver": "^6.3.1" }, "engines": { @@ -1848,17 +1785,17 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.7.tgz", - "integrity": "sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.25.9.tgz", + "integrity": "sha512-D3to0uSPiWE7rBrdIICCd0tJSIGpLaaGptna2+w7Pft5xMqLpA1sz99DK5TZ1TjGbdQ/VI1eCSZ06dv3lT4JOw==", "license": "MIT", "dependencies": { - "@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" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-react-display-name": "^7.25.9", + "@babel/plugin-transform-react-jsx": "^7.25.9", + "@babel/plugin-transform-react-jsx-development": "^7.25.9", + "@babel/plugin-transform-react-pure-annotations": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1867,11 +1804,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" - }, "node_modules/@babel/runtime": { "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", @@ -1889,30 +1821,30 @@ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", - "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", "license": "MIT", "dependencies": { - "@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", + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1921,37 +1853,31 @@ } }, "node_modules/@babel/types": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", - "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@bazel/runfiles": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@bazel/runfiles/-/runfiles-6.3.1.tgz", + "integrity": "sha512-1uLNT5NZsUVIGS4syuHwTzZ8HycMPyr6POA3FCE4GbMtc4rhoJk8aZKtNIRthJYfL+iioppi+rTfH3olMPr9nA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@choojs/findup": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@choojs/findup/-/findup-0.2.1.tgz", - "integrity": "sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw==", - "license": "MIT", - "dependencies": { - "commander": "^2.15.1" - }, - "bin": { - "findup": "bin/findup.js" - } - }, "node_modules/@cnakazawa/watch": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", @@ -2044,6 +1970,18 @@ "node": ">=10.0.0" } }, + "node_modules/@dual-bundle/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/@edx/brand-edx.org": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@edx/brand-edx.org/-/brand-edx.org-2.1.3.tgz", @@ -2085,21 +2023,24 @@ "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" }, "node_modules/@edx/edx-proctoring": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/@edx/edx-proctoring/-/edx-proctoring-4.18.1.tgz", - "integrity": "sha512-W8g8NFulXzGFjd2+ARGd8qbfYm7sPOQsCThPQBwivIYTRyPbcevjhUIe1yr7C1rgcBoYn9SsXPfZosNHLX61WQ==", - "hasShrinkwrap": true, + "version": "4.18.3", + "resolved": "https://registry.npmjs.org/@edx/edx-proctoring/-/edx-proctoring-4.18.3.tgz", + "integrity": "sha512-JeDT12uJYRumD7zIkjfemO40ZFkIhFqS5R6fGDO5MICRuoVJ/ndzcwTOhobcyGU/f8eMxlM1ukzWbSKBS+bUWg==", "license": "GNU Affero GPLv3" }, "node_modules/@edx/eslint-config": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@edx/eslint-config/-/eslint-config-3.2.0.tgz", - "integrity": "sha512-X2o34xr3KqmQSV/vJVv6k4FxUKYwbBATHTtTHLTYQvM9PVoM3WbKQP9tl6Z057pRErKzshJcks+4ENzDyhr11Q==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@edx/eslint-config/-/eslint-config-4.3.0.tgz", + "integrity": "sha512-4W9wFG4ALr3xocakCsncgJbK67RHfSmDwHDXKHReFtjxl/FRkxhS6qayz189oChqfANieeV3zRCLaq44bLf+/A==", "dev": true, + "license": "MIT", "peerDependencies": { - "eslint": "^6.8.0 || ^7.0.0 || ^8.0.0", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "eslint": "^7.32.0 || ^8.2.0", "eslint-config-airbnb": "^18.0.1 || ^19.0.0", - "eslint-plugin-import": "^2.20.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-plugin-import": "^2.25.3", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.18.0", "eslint-plugin-react-hooks": "^1.7.0 || ^4.0.0" @@ -2194,16 +2135,6 @@ "email-validator": "^2.0.4" } }, - "node_modules/@edx/frontend-component-cookie-policy-banner/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==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/@edx/frontend-component-cookie-policy-banner/node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -2221,7 +2152,7 @@ }, "node_modules/@edx/mockprock": { "version": "1.0.2", - "resolved": "git+ssh://git@github.com/openedx/mockprock.git#3ad18c6888e6521e9bf7a4df0db6f8579b928235", + "resolved": "git+ssh://git@github.com/openedx/mockprock.git#d70b05231bd46b0122616c24e209c890ef2633c0", "integrity": "sha512-47o2Wkyr6JDdoRNqUz+KocYY0Yvijmfp7EdaUbOBcT5hK8lqIe5yaH0LfbmSGx5gIrVVOkramUWu2RecxzZZ4Q==", "dev": true, "license": "Apache-2.0", @@ -2303,16 +2234,6 @@ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==" }, - "node_modules/@edx/studio-frontend/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==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/@edx/studio-frontend/node_modules/react-responsive": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-5.0.0.tgz", @@ -2955,6 +2876,22 @@ "stylelint": "^15.5.0" } }, + "node_modules/@edx/stylelint-config-edx/node_modules/stylelint-scss": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.7.0.tgz", + "integrity": "sha512-TSUgIeS0H3jqDZnby1UO1Qv3poi1N8wUYIJY6D1tuUq2MN3lwp/rITVo0wD+1SWTmRm0tNmGO0b7nKInnqF6Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "stylelint": "^14.5.1 || ^15.0.0" + } + }, "node_modules/@edx/stylelint-config-edx/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3183,16 +3120,6 @@ "react": ">=16.x" } }, - "node_modules/@fortawesome/react-fontawesome/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==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -4107,60 +4034,356 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "node_modules/@parcel/watcher": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz", + "integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==", + "hasInstallScript": true, + "license": "MIT", "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, "engines": { - "node": ">=14" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.0", + "@parcel/watcher-darwin-arm64": "2.5.0", + "@parcel/watcher-darwin-x64": "2.5.0", + "@parcel/watcher-freebsd-x64": "2.5.0", + "@parcel/watcher-linux-arm-glibc": "2.5.0", + "@parcel/watcher-linux-arm-musl": "2.5.0", + "@parcel/watcher-linux-arm64-glibc": "2.5.0", + "@parcel/watcher-linux-arm64-musl": "2.5.0", + "@parcel/watcher-linux-x64-glibc": "2.5.0", + "@parcel/watcher-linux-x64-musl": "2.5.0", + "@parcel/watcher-win32-arm64": "2.5.0", + "@parcel/watcher-win32-ia32": "2.5.0", + "@parcel/watcher-win32-x64": "2.5.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz", + "integrity": "sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz", + "integrity": "sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/popperjs" + "url": "https://opencollective.com/parcel" } }, - "node_modules/@restart/context": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", - "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==", - "peerDependencies": { - "react": ">=16.3.2" + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz", + "integrity": "sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@restart/hooks": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", - "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", - "dependencies": { - "dequal": "^2.0.3" + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz", + "integrity": "sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" }, - "peerDependencies": { - "react": ">=16.8.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dependencies": { - "type-detect": "4.0.8" + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz", + "integrity": "sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dependencies": { - "@sinonjs/commons": "^1.7.0" + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz", + "integrity": "sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tootallnate/once": { + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz", + "integrity": "sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz", + "integrity": "sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz", + "integrity": "sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz", + "integrity": "sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz", + "integrity": "sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz", + "integrity": "sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz", + "integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@restart/context": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", + "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==", + "peerDependencies": { + "react": ">=16.3.2" + } + }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", @@ -4223,10 +4446,31 @@ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "license": "MIT" }, "node_modules/@types/graceful-fs": { "version": "4.1.9", @@ -4295,13 +4539,6 @@ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "dev": true, - "peer": true - }, "node_modules/@types/prettier": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", @@ -4330,6 +4567,14 @@ "@types/react": "*" } }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -4353,67 +4598,339 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, - "peer": true - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "license": "MIT", + "peer": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -4580,9 +5097,10 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -4610,15 +5128,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -4688,16 +5197,6 @@ "react": "^0.14 || ^15.0.0 || ^16.0.0-alpha" } }, - "node_modules/airbnb-prop-types/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==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -4757,38 +5256,6 @@ "ajv": "^6.9.1" } }, - "node_modules/align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==", - "dependencies": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/align-text/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/align-text/node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "engines": { - "node": ">=0.10" - } - }, "node_modules/alphanum-sort": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", @@ -4887,6 +5354,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -4973,6 +5441,7 @@ "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.2.tgz", "integrity": "sha512-gUHx76KtnhEgB3HOuFYiCm3FIdEs6ocM2asHvNTkfu/Y09qQVrrVVaOKENmS2KkSaGoxgXNqC+ZVtR/n0MOkSA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -5151,18 +5620,40 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.toreversed": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", - "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", + "node_modules/array.prototype.reduce": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.7.tgz", + "integrity": "sha512-mzmiUCVwtiD4lgxYP8g7IYy8El8p2CSMePvIbTS7gchKir/L1fgJrk0yDKmAX6mnRQFKNADYIk8nNlTris5H1Q==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - } + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-array-method-boxes-properly": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", + "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } }, "node_modules/array.prototype.tosorted": { "version": "1.1.3", @@ -5214,16 +5705,12 @@ "node": ">=0.10.0" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" - }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": "~2.1.0" } @@ -5238,6 +5725,7 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8" } @@ -5288,7 +5776,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", @@ -5363,15 +5852,17 @@ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "*" } }, "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" }, "node_modules/axe-core": { "version": "4.7.0", @@ -5614,9 +6105,10 @@ } }, "node_modules/babel-loader": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", - "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "license": "MIT", "dependencies": { "find-cache-dir": "^4.0.0", "schema-utils": "^4.0.0" @@ -5765,12 +6257,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", - "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1", - "core-js-compat": "^3.36.1" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -6144,6 +6637,7 @@ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "tweetnacl": "^0.14.3" } @@ -6283,9 +6777,9 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "funding": [ { "type": "opencollective", @@ -6302,10 +6796,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -6527,24 +7021,6 @@ "node": ">=6" } }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, - "peer": true, - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/camelize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", @@ -6584,9 +7060,9 @@ "integrity": "sha512-BS+RAD1DggiDlE2KaBUWKsMDuVmmh3hCM5LI0OW25mGlPttGLeOjDUa1DmZvJVFCXvtshY4BTyFgv31eFTLg8g==" }, "node_modules/caniuse-lite": { - "version": "1.0.30001651", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", - "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", + "version": "1.0.30001680", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz", + "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==", "funding": [ { "type": "opencollective", @@ -6619,7 +7095,8 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/cast-array": { "version": "1.0.1", @@ -6629,18 +7106,6 @@ "isarray": "0.0.1" } }, - "node_modules/center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==", - "dependencies": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -6757,25 +7222,6 @@ "node": ">=0.10.0" } }, - "node_modules/chokidar/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, "node_modules/chokidar/node_modules/glob-parent": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", @@ -7005,9 +7451,10 @@ } }, "node_modules/classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" }, "node_modules/clean-stack": { "version": "2.2.0", @@ -7106,6 +7553,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" @@ -7411,12 +7859,12 @@ "hasInstallScript": true }, "node_modules/core-js-compat": { - "version": "3.38.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.0.tgz", - "integrity": "sha512-75LAicdLa4OJVwFxFbQR3NdnZjNgX6ILpVcVzcC4T2smerB5lELMrJQQQoWV6TiuC/vlaFqgU2tKQx9w5s0e0A==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", + "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", "license": "MIT", "dependencies": { - "browserslist": "^4.23.3" + "browserslist": "^4.24.2" }, "funding": { "type": "opencollective", @@ -7430,20 +7878,53 @@ "dev": true }, "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0", + "peer": true + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, "node_modules/cross-spawn": { @@ -7468,10 +7949,11 @@ } }, "node_modules/css-functions-list": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.1.tgz", - "integrity": "sha512-Nj5YcaGgBtuUmn1D7oHqPW0c9iui7xsTsj5lIX8ZgevdfhmjFfKB3r8moHJtNJnctnYXJyYX5I1pp90HM4TPgQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz", + "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12 || >=16" } @@ -7703,6 +8185,7 @@ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dev": true, + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" }, @@ -7780,19 +8263,21 @@ } }, "node_modules/datatables.net": { - "version": "1.13.11", - "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.13.11.tgz", - "integrity": "sha512-AE6RkMXziRaqzPcu/pl3SJXeRa6fmXQG/fVjuRESujvkzqDCYEeKTTpPMuVJSGYJpPi32WGSphVNNY1G4nSN/g==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.1.8.tgz", + "integrity": "sha512-47ULt+U4bcjbuGTpTlT6SnCuSFVRBxxdWa6X3NfvTObBJ2BZU0o+JUIl05wQ6cABNIavjbAV51gpgvFsMHL9zA==", + "license": "MIT", "dependencies": { - "jquery": "1.8 - 4" + "jquery": ">=1.7" } }, "node_modules/datatables.net-fixedcolumns": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/datatables.net-fixedcolumns/-/datatables.net-fixedcolumns-3.2.6.tgz", - "integrity": "sha512-PtEs2tllcHRVZj7fwmAQBWGJ5URRQZpDG2pJsh5jusvnRje3w1+KueMZm60iCtfOkIlUn+/j2+MghxLx/8yfKQ==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/datatables.net-fixedcolumns/-/datatables.net-fixedcolumns-5.0.4.tgz", + "integrity": "sha512-mYZ18oahD2oopcpBDmbSEZ2w+bna4TuTmmoaNd01JLEkJWsAH2yZ99fYslLrBX5Rku4GnCG5J8Sr3cJrrLw+3g==", + "license": "MIT", "dependencies": { - "datatables.net": "^1.10.15", + "datatables.net": "^2", "jquery": ">=1.7" } }, @@ -7803,11 +8288,12 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -8004,6 +8490,19 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -8177,6 +8676,7 @@ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dev": true, + "license": "MIT", "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -8186,7 +8686,8 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/edx-proctoring-proctortrack": { "version": "1.1.1", @@ -8198,9 +8699,9 @@ } }, "node_modules/edx-ui-toolkit": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/edx-ui-toolkit/-/edx-ui-toolkit-1.5.5.tgz", - "integrity": "sha512-kqqOgLonNZyGr1eXtkPKuOjZ5SCAuLbljoLdUV04OR+nk00t3Z4lbdcDu8WhTBVPqy8zK8v6HhhKbnpcH0Mxtg==", + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/edx-ui-toolkit/-/edx-ui-toolkit-1.8.6.tgz", + "integrity": "sha512-EkxjGLAUoXOs2cRZ6czSaywLofrZB8RXii943lGjfZsZAY0n0sNTZgAaGlKIxcPgiZYJppc79PKXGbYhRz9CNg==", "license": "Apache-2.0", "dependencies": { "backbone": "1.6.0", @@ -8209,7 +8710,7 @@ "jquery": "~3.7.0", "mini-css-extract-plugin": "^2.7.2", "moment": "2.30.1", - "moment-timezone": "0.5.45", + "moment-timezone": "0.5.46", "requirejs": "2.1.22", "requirejs-text": "2.0.16", "sass": "^1.58.3", @@ -8236,7 +8737,8 @@ "node_modules/edx-ui-toolkit/node_modules/lolex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz", - "integrity": "sha512-YYp8cqz7/8eruZ15L1mzcPkvLYxipfdsWIDESvNdNmQP9o7TsDitRhNuV2xb7aFu2ofZngao1jiVrVZ842x4BQ==" + "integrity": "sha512-YYp8cqz7/8eruZ15L1mzcPkvLYxipfdsWIDESvNdNmQP9o7TsDitRhNuV2xb7aFu2ofZngao1jiVrVZ842x4BQ==", + "license": "BSD-3-Clause" }, "node_modules/edx-ui-toolkit/node_modules/requirejs": { "version": "2.1.22", @@ -8260,6 +8762,7 @@ "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.7.tgz", "integrity": "sha512-M9rtyQxKfcTTdB64rpPSRaTzOvunb+HHPv/3PxvNPrEDnFSny95Pi6/3VoD471ody0ay0IHyzT3BErfcLXj6NA==", "deprecated": "16.1.1", + "license": "BSD-3-Clause", "dependencies": { "formatio": "1.1.1", "lolex": "1.3.2", @@ -8283,9 +8786,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.6.tgz", - "integrity": "sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw==", + "version": "1.5.63", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.63.tgz", + "integrity": "sha512-ddeXKuY9BHo/mw145axlyWjlJ1UBt4WK3AlvkT7W2AbqfRQoacVoRUCF6wL3uIx/8wT9oLKXzI+rFqHHscByaA==", "license": "ISC" }, "node_modules/email-prop-type": { @@ -8342,6 +8845,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -8350,6 +8854,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -8521,6 +9026,19 @@ "node": ">=4" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/enzyme": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", @@ -8580,18 +9098,6 @@ "react-dom": "^16.0.0-0" } }, - "node_modules/enzyme-adapter-react-16/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/enzyme-adapter-react-16/node_modules/semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", @@ -8623,28 +9129,18 @@ "react": "0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0" } }, - "node_modules/enzyme-adapter-utils/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, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/enzyme-matchers": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/enzyme-matchers/-/enzyme-matchers-6.1.2.tgz", - "integrity": "sha512-cP9p+HMOZ1ZXQ+k2H4dCkxmTZzIvpEy5zv0ZjgoBl6D0U43v+bJGH5IeWHdIovCzgJ0dVcMCKJ6lNu83lYUCAA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/enzyme-matchers/-/enzyme-matchers-7.1.2.tgz", + "integrity": "sha512-03WqAg2XDl7id9rARIO97HQ1JIw9F2heJ3R4meGu/13hx0ULTDEgl0E67MGl2Uq1jq1DyRnJfto1/VSzskdV5A==", "dev": true, + "license": "MIT", "dependencies": { "circular-json-es6": "^2.0.1", "deep-equal-ident": "^1.1.1" }, "peerDependencies": { - "enzyme": "3.x" + "enzyme": ">=3.4.0" } }, "node_modules/enzyme-shallow-equal": { @@ -8938,9 +9434,9 @@ } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" @@ -9139,6 +9635,23 @@ "eslint-plugin-import": "^2.25.2" } }, + "node_modules/eslint-config-airbnb-typescript": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", + "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.13.0 || ^6.0.0", + "@typescript-eslint/parser": "^5.0.0 || ^6.0.0", + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -9162,13 +9675,12 @@ } }, "node_modules/eslint-import-resolver-webpack": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.13.8.tgz", - "integrity": "sha512-Y7WIaXWV+Q21Rz/PJgUxiW/FTBOWmU8NTLdz+nz9mMoiz5vAev/fOaQxwD7qRzTfE3HSm1qsxZ5uRd7eX+VEtA==", + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.13.9.tgz", + "integrity": "sha512-yGngeefNiHXau2yzKKs2BNON4HLpxBabY40BGL/vUSKZtqzjlVsTTZm57jhHULhm+mJEwKsEIIN3NXup5AiiBQ==", "dev": true, "license": "MIT", "dependencies": { - "array.prototype.find": "^2.2.2", "debug": "^3.2.7", "enhanced-resolve": "^0.9.1", "find-root": "^1.1.0", @@ -9399,18 +9911,6 @@ "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-react/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, - "peer": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/eslint-plugin-react/node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", @@ -9760,7 +10260,8 @@ "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true }, "node_modules/events": { "version": "3.3.0", @@ -10013,7 +10514,8 @@ "dev": true, "engines": [ "node >=0.6.0" - ] + ], + "license": "MIT" }, "node_modules/fancy-log": { "version": "1.3.3", @@ -10106,26 +10608,6 @@ "bser": "2.1.1" } }, - "node_modules/fbjs": { - "version": "0.8.18", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.18.tgz", - "integrity": "sha512-EQaWFK+fEPSoibjNy8IxUtaFOMXcWsY0JaVrQoZR9zC8N2Ygf9iDITPWjUTVIax95b6I742JFLqASHfsag/vKA==", - "dependencies": { - "core-js": "^1.0.0", - "isomorphic-fetch": "^2.1.1", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.30" - } - }, - "node_modules/fbjs/node_modules/core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js." - }, "node_modules/figures": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", @@ -10474,10 +10956,26 @@ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "*" } }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/formatio": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", @@ -10553,6 +11051,26 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "Upgrade to fsevents v2 to mitigate potential security issues", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -10621,6 +11139,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -10710,6 +11241,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dev": true, + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" } @@ -11021,24 +11553,12 @@ "uglify-js": "^3.1.4" } }, - "node_modules/handlebars/node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", "dev": true, + "license": "ISC", "engines": { "node": ">=4" } @@ -11049,6 +11569,7 @@ "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "deprecated": "this library is no longer supported", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.3", "har-schema": "^2.0.0" @@ -11268,13 +11789,10 @@ } }, "node_modules/hls.js": { - "version": "0.14.17", - "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-0.14.17.tgz", - "integrity": "sha512-25A7+m6qqp6UVkuzUQ//VVh2EEOPYlOBg32ypr34bcPO7liBMOkKFvbjbCBfiPAOTA/7BSx1Dujft3Th57WyFg==", - "dependencies": { - "eventemitter3": "^4.0.3", - "url-toolkit": "^2.1.6" - } + "version": "1.5.17", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.17.tgz", + "integrity": "sha512-wA66nnYFvQa1o4DO/BFgLNRKnBTVXpNeldGRBJ2Y0SvFtdwvFKCbqa9zhHoZLoxHhZ+jYsj3aIBkWQQCPNOhMw==", + "license": "Apache-2.0" }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", @@ -11418,6 +11936,7 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "dev": true, + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -11508,9 +12027,10 @@ "license": "MIT" }, "node_modules/immutable": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", - "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==" + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.2.tgz", + "integrity": "sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw==", + "license": "MIT" }, "node_modules/import-fresh": { "version": "3.3.0", @@ -11578,20 +12098,16 @@ } }, "node_modules/imports-loader": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-0.7.1.tgz", - "integrity": "sha512-tQ1upp3IcLH8YRk9b9PlJi7BsAGQhGRlgyLil6uZ/6kFb7C0H9YH9XwDVIjxl/TxFgQ0Wkrx8VRt1Ff2Vf6Mag==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-0.8.0.tgz", + "integrity": "sha512-kXWL7Scp8KQ4552ZcdVTeaQCZSLW+e6nJfp3cwUMB673T7Hr98Xjx5JK+ql7ADlJUvj1JS5O01RLbKoutN5QDQ==", + "license": "MIT", "dependencies": { "loader-utils": "^1.0.2", - "source-map": "^0.5.6" - } - }, - "node_modules/imports-loader/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "source-map": "^0.6.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 4" } }, "node_modules/imurmurhash": { @@ -11828,13 +12344,13 @@ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" }, "node_modules/irregular-plurals": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-2.0.0.tgz", - "integrity": "sha512-Y75zBYLkh0lJ9qxeHlMjQ7bSbyiSqNW/UOPWDmzC7cXskL1hekSITh1Oc6JV0XCWWZ9DE8VYSB71xocLk3gmGw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", + "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/is-absolute-url": { @@ -11935,7 +12451,8 @@ "node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true }, "node_modules/is-callable": { "version": "1.2.7", @@ -12028,7 +12545,6 @@ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, - "optional": true, "bin": { "is-docker": "cli.js" }, @@ -12073,6 +12589,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -12139,6 +12656,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "devOptional": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -12398,6 +12916,19 @@ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", @@ -12450,7 +12981,6 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, - "optional": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -12498,20 +13028,12 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true }, - "node_modules/isomorphic-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA==", - "dependencies": { - "node-fetch": "^1.0.1", - "whatwg-fetch": ">=0.10.0" - } - }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/istanbul": { "version": "0.4.5", @@ -13253,12 +13775,13 @@ } }, "node_modules/jest-environment-enzyme": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/jest-environment-enzyme/-/jest-environment-enzyme-6.1.2.tgz", - "integrity": "sha512-WHeBKgBYOdryuOTEoK55lJwjg7Raery1OgXHLwukI3mSYgOkm2UrCDDT+vneqVgy7F8KuRHyStfD+TC/m2b7Kg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/jest-environment-enzyme/-/jest-environment-enzyme-7.1.2.tgz", + "integrity": "sha512-3tfaYAzO7qZSRrv+srQnfK16Vu5XwH/pHi8FpoqSHjKKngbHzXf7aBCBuWh8y3w0OtknHRfDMFrC60Khj+g1hA==", "dev": true, + "license": "MIT", "dependencies": { - "jest-environment-jsdom": "^22.4.1" + "jest-environment-jsdom": "^24.0.0" }, "peerDependencies": { "enzyme": "3.x", @@ -13266,11 +13789,159 @@ "react": "^0.13.0 || ^0.14.0 || ^15.0.0 || >=16.x" } }, + "node_modules/jest-environment-enzyme/node_modules/@jest/console": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", + "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/source-map": "^24.9.0", + "chalk": "^2.0.1", + "slash": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/@jest/environment": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.9.0.tgz", + "integrity": "sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^24.9.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "jest-mock": "^24.9.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/@jest/fake-timers": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz", + "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-mock": "^24.9.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/@jest/source-map": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz", + "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0", + "graceful-fs": "^4.1.15", + "source-map": "^0.6.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/@jest/test-result": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz", + "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/istanbul-lib-coverage": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/@jest/transform": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz", + "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/types": "^24.9.0", + "babel-plugin-istanbul": "^5.1.0", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.15", + "jest-haste-map": "^24.9.0", + "jest-regex-util": "^24.9.0", + "jest-util": "^24.9.0", + "micromatch": "^3.1.10", + "pirates": "^4.0.1", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "2.4.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/@types/istanbul-reports": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", + "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/jest-environment-enzyme/node_modules/@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-environment-enzyme/node_modules/@types/yargs": { + "version": "13.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.12.tgz", + "integrity": "sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, "node_modules/jest-environment-enzyme/node_modules/acorn": { "version": "5.7.4", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -13283,6 +13954,7 @@ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", "dev": true, + "license": "MIT", "dependencies": { "acorn": "^6.0.1", "acorn-walk": "^6.0.1" @@ -13293,6 +13965,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -13305,50 +13978,106 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } }, + "node_modules/jest-environment-enzyme/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "license": "ISC", + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/jest-environment-enzyme/node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-environment-enzyme/node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-environment-enzyme/node_modules/babel-plugin-istanbul": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", + "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.3.0", + "test-exclude": "^5.2.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jest-environment-enzyme/node_modules/braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha512-xU7bpz2ytJl1bH9cgIurjpg/n8Gohy9GTw81heDYLJQ4RU60dlyJsa+atVF2pI0yMMvKxI9HkKwjePCj5XI1hw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, + "license": "MIT", "dependencies": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/jest-environment-enzyme/node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "node_modules/jest-environment-enzyme/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/jest-environment-enzyme/node_modules/ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - }, "node_modules/jest-environment-enzyme/node_modules/cssom": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jest-environment-enzyme/node_modules/cssstyle": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", "dev": true, + "license": "MIT", "dependencies": { "cssom": "0.3.x" } @@ -13358,6 +14087,7 @@ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", "dev": true, + "license": "MIT", "dependencies": { "abab": "^2.0.0", "whatwg-mimetype": "^2.2.0", @@ -13369,18 +14099,30 @@ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", "dev": true, + "license": "MIT", "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, + "node_modules/jest-environment-enzyme/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, "node_modules/jest-environment-enzyme/node_modules/domexception": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", "deprecated": "Use your platform's native DOMException instead", "dev": true, + "license": "MIT", "dependencies": { "webidl-conversions": "^4.0.2" } @@ -13390,6 +14132,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -13399,6 +14142,7 @@ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esprima": "^4.0.1", "estraverse": "^4.2.0", @@ -13421,107 +14165,366 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, - "node_modules/jest-environment-enzyme/node_modules/expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==", + "node_modules/jest-environment-enzyme/node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^2.1.0" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/jest-environment-enzyme/node_modules/html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "node_modules/jest-environment-enzyme/node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", "dev": true, + "license": "MIT", "dependencies": { - "whatwg-encoding": "^1.0.1" + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/jest-environment-enzyme/node_modules/is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "node_modules/jest-environment-enzyme/node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", "dev": true, + "license": "MIT", "dependencies": { - "ci-info": "^1.5.0" + "is-extendable": "^0.1.0" }, - "bin": { - "is-ci": "bin.js" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/jest-environment-enzyme/node_modules/is-extglob": { + "node_modules/jest-environment-enzyme/node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jest-environment-enzyme/node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-environment-enzyme/node_modules/extglob/node_modules/define-property": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^1.0.0" + }, "engines": { "node": ">=0.10.0" } }, - "node_modules/jest-environment-enzyme/node_modules/is-glob": { + "node_modules/jest-environment-enzyme/node_modules/extglob/node_modules/extend-shallow": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", "dev": true, + "license": "MIT", "dependencies": { - "is-extglob": "^1.0.0" + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-environment-enzyme/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-environment-enzyme/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-environment-enzyme/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^1.0.1" + } + }, + "node_modules/jest-environment-enzyme/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" }, "engines": { "node": ">=0.10.0" } }, + "node_modules/jest-environment-enzyme/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-environment-enzyme/node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-environment-enzyme/node_modules/istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jest-environment-enzyme/node_modules/jest-environment-jsdom": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz", - "integrity": "sha512-FviwfR+VyT3Datf13+ULjIMO5CSeajlayhhYQwpzgunswoaLIPutdbrnfUHEMyJCwvqQFaVtTmn9+Y8WCt6n1w==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz", + "integrity": "sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==", "dev": true, + "license": "MIT", "dependencies": { - "jest-mock": "^22.4.3", - "jest-util": "^22.4.3", + "@jest/environment": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/types": "^24.9.0", + "jest-mock": "^24.9.0", + "jest-util": "^24.9.0", "jsdom": "^11.5.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/jest-haste-map": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz", + "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^24.9.0", + "anymatch": "^2.0.0", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.1.15", + "invariant": "^2.2.4", + "jest-serializer": "^24.9.0", + "jest-util": "^24.9.0", + "jest-worker": "^24.9.0", + "micromatch": "^3.1.10", + "sane": "^4.0.3", + "walker": "^1.0.7" + }, + "engines": { + "node": ">= 6" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" } }, "node_modules/jest-environment-enzyme/node_modules/jest-message-util": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-22.4.3.tgz", - "integrity": "sha512-iAMeKxhB3Se5xkSjU0NndLLCHtP4n+GtCqV0bISKA5dmOXQfEbdEmYiu2qpnWBDCQdEafNDDU6Q+l6oBMd/+BA==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", + "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.0.0-beta.35", + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/stack-utils": "^1.0.1", "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", + "micromatch": "^3.1.10", + "slash": "^2.0.0", "stack-utils": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/jest-environment-enzyme/node_modules/jest-mock": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-22.4.3.tgz", - "integrity": "sha512-+4R6mH5M1G4NK16CKg9N1DtCaFmuxhcIqF4lQK/Q1CIotqMs/XBemfpDPeVZBFow6iyUNu6EBT9ugdNOTT5o5Q==", - "dev": true + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz", + "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^24.9.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/jest-regex-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", + "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/jest-serializer": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz", + "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } }, "node_modules/jest-environment-enzyme/node_modules/jest-util": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-22.4.3.tgz", - "integrity": "sha512-rfDfG8wyC5pDPNdcnAlZgwKnzHvZDu8Td2NJI/jAGKEGxJPYiE4F0ss/gSAkG4778Y23Hvbz+0GMrDJTeo7RjQ==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz", + "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==", "dev": true, + "license": "MIT", "dependencies": { - "callsites": "^2.0.0", + "@jest/console": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/source-map": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "callsites": "^3.0.0", "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^22.4.3", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", "mkdirp": "^0.5.1", + "slash": "^2.0.0", "source-map": "^0.6.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + }, + "engines": { + "node": ">= 6" } }, "node_modules/jest-environment-enzyme/node_modules/jsdom": { @@ -13529,6 +14532,7 @@ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", "dev": true, + "license": "MIT", "dependencies": { "abab": "^2.0.0", "acorn": "^5.5.3", @@ -13558,23 +14562,12 @@ "xml-name-validator": "^3.0.0" } }, - "node_modules/jest-environment-enzyme/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/jest-environment-enzyme/node_modules/levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -13583,35 +14576,74 @@ "node": ">= 0.8.0" } }, + "node_modules/jest-environment-enzyme/node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jest-environment-enzyme/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jest-environment-enzyme/node_modules/micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha512-LnU2XFEk9xxSJ6rfgAry/ty5qwUTyHYOBU0g4R6tIw5ljwgGIBmiKhRWLw5NpMOnrgUNcDJ4WMp8rl3sYVHLNA==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, + "license": "MIT", "dependencies": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" }, "engines": { "node": ">=0.10.0" } }, + "node_modules/jest-environment-enzyme/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/jest-environment-enzyme/node_modules/normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", "dev": true, + "license": "MIT", "dependencies": { "remove-trailing-separator": "^1.0.1" }, @@ -13624,6 +14656,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "dev": true, + "license": "MIT", "dependencies": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.6", @@ -13636,11 +14669,72 @@ "node": ">= 0.8.0" } }, + "node_modules/jest-environment-enzyme/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/jest-environment-enzyme/node_modules/parse5": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/jest-environment-enzyme/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/jest-environment-enzyme/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jest-environment-enzyme/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } }, "node_modules/jest-environment-enzyme/node_modules/prelude-ls": { "version": "1.1.2", @@ -13651,34 +14745,119 @@ "node": ">= 0.8.0" } }, - "node_modules/jest-environment-enzyme/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "node_modules/jest-environment-enzyme/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jest-environment-enzyme/node_modules/read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/jest-environment-enzyme/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/jest-environment-enzyme/node_modules/stack-utils": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.5.tgz", + "integrity": "sha512-KZiTzuV3CnSnSvgMRrARVCj+Ht7rMbauGDK0LdVFRGyenwdylpajAp4Q0i6SX8rEmbTpMMf6ryq2gb8pPq2WgQ==", "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-enzyme/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/jest-environment-enzyme/node_modules/supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, "engines": { "node": ">=6" } }, - "node_modules/jest-environment-enzyme/node_modules/slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", + "node_modules/jest-environment-enzyme/node_modules/test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/jest-environment-enzyme/node_modules/stack-utils": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.5.tgz", - "integrity": "sha512-KZiTzuV3CnSnSvgMRrARVCj+Ht7rMbauGDK0LdVFRGyenwdylpajAp4Q0i6SX8rEmbTpMMf6ryq2gb8pPq2WgQ==", + "node_modules/jest-environment-enzyme/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", "dev": true, + "license": "MIT", "dependencies": { - "escape-string-regexp": "^2.0.0" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, "node_modules/jest-environment-enzyme/node_modules/tough-cookie": { @@ -13686,6 +14865,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "psl": "^1.1.28", "punycode": "^2.1.1" @@ -13699,6 +14879,7 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", "dev": true, + "license": "MIT", "dependencies": { "punycode": "^2.1.0" } @@ -13708,6 +14889,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "~1.1.2" }, @@ -13719,24 +14901,39 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true + "dev": true, + "license": "BSD-2-Clause" }, "node_modules/jest-environment-enzyme/node_modules/whatwg-url": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", "dev": true, + "license": "MIT", "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, + "node_modules/jest-environment-enzyme/node_modules/write-file-atomic": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", + "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, "node_modules/jest-environment-enzyme/node_modules/ws": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.3.tgz", - "integrity": "sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.4.tgz", + "integrity": "sha512-fFCejsuC8f9kOSu9FYaOw8CdO68O3h5v0lg4p74o8JqWpwTf9tniOD+nOB78aWoVSS6WptVUmDrp/KPsMVBWFQ==", "dev": true, + "license": "MIT", "dependencies": { "async-limiter": "~1.0.0" } @@ -13776,18 +14973,18 @@ } }, "node_modules/jest-enzyme": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-6.1.2.tgz", - "integrity": "sha512-+ds7r2ru3QkNJxelQ2tnC6d33pjUSsZHPD3v4TlnHlNMuGX3UKdxm5C46yZBvJICYBvIF+RFKBhLMM4evNM95Q==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/jest-enzyme/-/jest-enzyme-7.1.2.tgz", + "integrity": "sha512-j+jkph3t5hGBS12eOldpfsnERYRCHi4c/0KWPMnqRPoJJXvCpLIc5th1MHl0xDznQDXVU0AHUXg3rqMrf8vGpA==", "dev": true, "license": "MIT", "dependencies": { - "enzyme-matchers": "^6.1.2", + "enzyme-matchers": "^7.1.2", "enzyme-to-json": "^3.3.0", - "jest-environment-enzyme": "^6.1.2" + "jest-environment-enzyme": "^7.1.2" }, "peerDependencies": { - "enzyme": "3.x", + "enzyme": ">=3.4.0", "jest": ">=22.0.0" } }, @@ -13827,6 +15024,21 @@ "fsevents": "^2.1.2" } }, + "node_modules/jest-haste-map/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/jest-jasmine2": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", @@ -15024,14 +16236,15 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/jshint": { @@ -15185,6 +16398,13 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -15194,7 +16414,8 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -15234,7 +16455,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/json2mq": { "version": "0.2.0", @@ -15293,6 +16515,7 @@ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "dev": true, + "license": "MIT", "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -15636,10 +16859,31 @@ } }, "node_modules/karma-firefox-launcher": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-0.1.7.tgz", - "integrity": "sha512-jS+2RpaVUlEojyWJWsL5MVSxW0h6dRiQ0bpT19bYmLejkI3qCENWU6+xkCOc8d3/K1l6h2mkz+XCi2KQqOyncQ==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.3.tgz", + "integrity": "sha512-LMM2bseebLbYjODBOVt7TCPP9OI2vZIXCavIXhkO9m+10Uj5l7u/SKoeRmYx8FYHTVGZSpk6peX+3BMHC1WwNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^2.2.0", + "which": "^3.0.0" + } + }, + "node_modules/karma-firefox-launcher/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/karma-jasmine": { "version": "0.3.8", @@ -15776,15 +17020,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/karma-webpack/node_modules/webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", - "dev": true, - "dependencies": { - "lodash": "^4.17.15" - } - }, "node_modules/karma/node_modules/lodash": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", @@ -15849,10 +17084,11 @@ } }, "node_modules/known-css-properties": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz", - "integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz", + "integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/language-subtag-registry": { @@ -15875,20 +17111,13 @@ "node": ">=0.10" } }, - "node_modules/lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/left-pad": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", "deprecated": "use String.prototype.padStart()", - "dev": true + "dev": true, + "license": "WTFPL" }, "node_modules/leven": { "version": "3.1.0", @@ -16125,7 +17354,8 @@ "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.template": { "version": "4.5.0", @@ -16158,16 +17388,20 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" }, "node_modules/log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.0.tgz", + "integrity": "sha512-zrc91EDk2M+2AXo/9BTvK91pqb7qrPg2nX/Hy+u8a5qQlbaOflCKO+6SqgZ+M+xUFxGdKTgwnGiL96b1W3ikRA==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^2.4.2" + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/log4js": { @@ -16199,14 +17433,6 @@ "integrity": "sha512-/bpxDL56TG5LS5zoXxKqA6Ro5tkOS5M8cm/7yQcwLIKIcM2HR5fjjNCaIhJNv96SEk4hNGSafYMZK42Xv5fihQ==", "dev": true }, - "node_modules/longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -16413,110 +17639,19 @@ "dev": true }, "node_modules/meow": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", - "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize": "^1.2.0", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "peer": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "peer": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "peer": true, - "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "peer": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, + "license": "MIT", "peer": true, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/meow/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "peer": true - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -16532,11 +17667,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -16544,20 +17680,22 @@ } }, "node_modules/micromatch/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/micromatch/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -16691,9 +17829,10 @@ } }, "node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -16901,9 +18040,10 @@ } }, "node_modules/moment-timezone": { - "version": "0.5.45", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz", - "integrity": "sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==", + "version": "0.5.46", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.46.tgz", + "integrity": "sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==", + "license": "MIT", "dependencies": { "moment": "^2.29.4" }, @@ -16918,9 +18058,10 @@ "dev": true }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/mute-stream": { "version": "0.0.5", @@ -16939,7 +18080,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -17014,6 +18154,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/nearley": { "version": "2.20.1", "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", @@ -17061,27 +18209,18 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "node_modules/node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "dependencies": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - }, - "node_modules/node-fetch/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "engines": { - "node": ">=0.10.0" - } + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true }, "node_modules/node-gyp": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.0.1.tgz", - "integrity": "sha512-gg3/bHehQfZivQVfqIyy8wTdSymF9yTyP4CJifK73imyNMU8AIGQE2pUa7dNWfmMeG9cDVF2eehiRMv0LC1iAg==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", + "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", + "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", @@ -17089,9 +18228,9 @@ "graceful-fs": "^4.2.6", "make-fetch-happen": "^13.0.0", "nopt": "^7.0.0", - "proc-log": "^3.0.0", + "proc-log": "^4.1.0", "semver": "^7.3.5", - "tar": "^6.1.2", + "tar": "^6.2.1", "which": "^4.0.0" }, "bin": { @@ -17105,6 +18244,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -17113,27 +18253,27 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/node-gyp/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } @@ -17142,25 +18282,31 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "license": "ISC", "engines": { "node": ">=16" } }, - "node_modules/node-gyp/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/node-gyp/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", "dependencies": { - "yallist": "^4.0.0" + "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/node-gyp/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -17172,9 +18318,10 @@ } }, "node_modules/node-gyp/node_modules/nopt": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", - "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "license": "ISC", "dependencies": { "abbrev": "^2.0.0" }, @@ -17186,12 +18333,10 @@ } }, "node_modules/node-gyp/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -17203,6 +18348,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, @@ -17213,11 +18359,6 @@ "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/node-gyp/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -17318,6 +18459,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -17413,6 +18555,7 @@ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "*" } @@ -17587,6 +18730,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.8.tgz", + "integrity": "sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.reduce": "^1.0.6", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "gopd": "^1.0.1", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object.groupby": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", @@ -17847,6 +19012,12 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -18041,15 +19212,16 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -18087,9 +19259,9 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { @@ -18745,16 +19917,19 @@ } }, "node_modules/plur": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/plur/-/plur-3.1.1.tgz", - "integrity": "sha512-t1Ax8KUvV3FFII8ltczPn2tJdjqbd1sIzu6t4JL7nQ3EyeL/lTrj5PWKb06ic5/6XYDr65rQ4uzQEGN70/6X5w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", + "integrity": "sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==", "dev": true, "license": "MIT", "dependencies": { - "irregular-plurals": "^2.0.0" + "irregular-plurals": "^3.3.0" }, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/pluralize": { @@ -18767,7 +19942,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/point-in-polygon": { "version": "0.0.0", @@ -19144,10 +20320,11 @@ } }, "node_modules/postcss-resolve-nested-selector": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", - "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", - "dev": true + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", + "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==", + "dev": true, + "license": "MIT" }, "node_modules/postcss-selector-parser": { "version": "2.2.3", @@ -19349,9 +20526,10 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, "node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -19371,14 +20549,6 @@ "node": ">=0.4.0" } }, - "node_modules/promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dependencies": { - "asap": "~2.0.3" - } - }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", @@ -19405,13 +20575,14 @@ } }, "node_modules/prop-types": { - "version": "15.6.0", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.0.tgz", - "integrity": "sha512-H16NHdiZ8szYSKNkCpmKmS8BCogxyABjJ1AqQknIY2iTpy1xC04egoBAzjKm+WU2pbuNxFonw921dnxR0QYAdw==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", "dependencies": { - "fbjs": "^0.8.16", - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" } }, "node_modules/prop-types-exact": { @@ -19457,6 +20628,15 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/q": { "version": "0.9.7", "resolved": "https://registry.npmjs.org/q/-/q-0.9.7.tgz", @@ -19518,16 +20698,6 @@ } ] }, - "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -19603,9 +20773,56 @@ } }, "node_modules/raw-loader": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", - "integrity": "sha512-sf7oGoLuaYAScB4VGr0tzetsYlS8EJH6qnTCfQ/WVEa89hALQ4RQfCKt5xCyPQKPDUbVUAIP1QsxAwfAjlDp7Q==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", + "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==", + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/raw-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/raw-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } }, "node_modules/rbush": { "version": "1.4.3", @@ -19662,16 +20879,6 @@ "csstype": "^3.0.2" } }, - "node_modules/react-bootstrap/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==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/react-bootstrap/node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -19712,16 +20919,6 @@ "react": "^16.14.0" } }, - "node_modules/react-dom/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==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/react-dropzone": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-4.3.0.tgz", @@ -19753,16 +20950,6 @@ "react": "^15.0.0 || ^16.0.0" } }, - "node_modules/react-focus-lock/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==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/react-focus-on": { "version": "3.9.2", "resolved": "https://registry.npmjs.org/react-focus-on/-/react-focus-on-3.9.2.tgz", @@ -19800,16 +20987,6 @@ "node": ">=10" } }, - "node_modules/react-focus-on/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==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/react-focus-on/node_modules/react-focus-lock": { "version": "2.11.2", "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.11.2.tgz", @@ -19897,43 +21074,30 @@ "csstype": "^3.0.2" } }, - "node_modules/react-overlays/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==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/react-proptype-conditional-require": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/react-proptype-conditional-require/-/react-proptype-conditional-require-1.0.4.tgz", "integrity": "sha512-nopsRn7KnGgazBe2c3H2+Kf+Csp6PGDRLiBkYEDMKY8o/EIgft/WnIm/OnAKTawZiLnJXHAqhpFBddvs6NiXlw==" }, "node_modules/react-redux": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz", - "integrity": "sha512-5VI8EV5hdgNgyjfmWzBbdrqUkrVRKlyTKk1sGH3jzM2M2Mhj/seQgPXaz6gVAj2lz/nz688AdTqMO18Lr24Zhg==", - "dependencies": { - "hoist-non-react-statics": "^2.5.0", - "invariant": "^2.0.0", - "lodash": "^4.17.5", - "lodash-es": "^4.17.5", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.1.2.tgz", + "integrity": "sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.2", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4", "loose-envify": "^1.1.0", - "prop-types": "^15.6.0" + "prop-types": "^15.6.1", + "react-is": "^16.6.0", + "react-lifecycles-compat": "^3.0.0" }, "peerDependencies": { "react": "^0.14.0 || ^15.0.0-0 || ^16.0.0-0", "redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0" } }, - "node_modules/react-redux/node_modules/hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - }, "node_modules/react-remove-scroll": { "version": "2.5.9", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.9.tgz", @@ -19995,16 +21159,6 @@ "react": "^16.3.0" } }, - "node_modules/react-responsive/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==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/react-router": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz", @@ -20042,30 +21196,11 @@ "react": ">=15" } }, - "node_modules/react-router-dom/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==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/react-router/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==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/react-slick": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.29.0.tgz", - "integrity": "sha512-TGdOKE+ZkJHHeC4aaoH85m8RnFyWqdqRfAGkhd6dirmATXMZWAxOpTLmw2Ll/jPTQ3eEG7ercFr/sbzdeYCJXA==", + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.30.2.tgz", + "integrity": "sha512-XvQJi7mRHuiU3b9irsqS9SGIgftIfdV5/tNcURTb5LdIokRA5kIIx3l4rlq2XYHfxcSntXapoRg/GxaVOM1yfg==", + "license": "MIT", "dependencies": { "classnames": "^2.2.5", "enquire.js": "^2.1.6", @@ -20128,51 +21263,19 @@ "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": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", - "dependencies": { - "dom-helpers": "^3.4.0", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" - }, - "peerDependencies": { - "react": ">=15.0.0", - "react-dom": ">=15.0.0" - } - }, - "node_modules/react-transition-group/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==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/react/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==", + "node_modules/react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", "dependencies": { + "dom-helpers": "^3.4.0", "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0", + "react-dom": ">=15.0.0" } }, "node_modules/read-pkg": { @@ -20586,30 +21689,29 @@ "node": ">=0.10.0" } }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "node_modules/realpath-native": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", "dev": true, + "license": "MIT", "dependencies": { - "resolve": "^1.20.0" + "util.promisify": "^1.0.0" }, "engines": { - "node": ">= 10.13.0" + "node": ">=4" } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, - "peer": true, "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "resolve": "^1.20.0" }, "engines": { - "node": ">=8" + "node": ">= 10.13.0" } }, "node_modules/reduce-css-calc": { @@ -20690,12 +21792,14 @@ "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "license": "MIT", "dependencies": { "regenerate": "^1.4.2" }, @@ -20759,14 +21863,15 @@ } }, "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", + "integrity": "sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==", + "license": "MIT", "dependencies": { - "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.11.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" }, @@ -20774,25 +21879,24 @@ "node": ">=4" } }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.2.tgz", + "integrity": "sha512-3OGZZ4HoLJkkAZx/48mTXJNlmqTGOzc0o9OWQPuWpkOlXXPbyN6OafCcoXUnBqE2D3f/T5L+pWc1kdEmnfnRsA==", + "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~0.5.0" + "jsesc": "~3.0.2" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "bin": { - "jsesc": "bin/jsesc" - } - }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -20835,6 +21939,7 @@ "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", "dev": true, + "license": "Apache-2.0", "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -20866,6 +21971,7 @@ "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", "dev": true, + "license": "ISC", "dependencies": { "lodash": "^4.17.19" }, @@ -20882,6 +21988,7 @@ "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", "dev": true, + "license": "ISC", "dependencies": { "request-promise-core": "1.1.4", "stealthy-require": "^1.1.1", @@ -20894,20 +22001,12 @@ "request": "^2.34" } }, - "node_modules/request-promise-native/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/request-promise-native/node_modules/tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "psl": "^1.1.28", "punycode": "^2.1.1" @@ -20916,34 +22015,12 @@ "node": ">=0.8" } }, - "node_modules/request/node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/request/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/request/node_modules/qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.6" } @@ -20953,6 +22030,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "psl": "^1.1.28", "punycode": "^2.1.1" @@ -20967,6 +22045,7 @@ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", "dev": true, + "license": "MIT", "bin": { "uuid": "bin/uuid" } @@ -21147,17 +22226,6 @@ "node": ">=0.10.0" } }, - "node_modules/right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==", - "dependencies": { - "align-text": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -21193,42 +22261,49 @@ } }, "node_modules/rtlcss": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-2.6.2.tgz", - "integrity": "sha512-06LFAr+GAPo+BvaynsXRfoYTJvSaWRyOhURCQ7aeI1MKph9meM222F+Zkt3bDamyHHJuGi3VPtiRkpyswmQbGA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", "license": "MIT", "dependencies": { - "@choojs/findup": "^0.2.1", - "chalk": "^2.4.2", - "mkdirp": "^0.5.1", - "postcss": "^6.0.23", - "strip-json-comments": "^2.0.0" + "escalade": "^3.1.1", + "picocolors": "^1.0.0", + "postcss": "^8.4.21", + "strip-json-comments": "^3.1.1" }, "bin": { "rtlcss": "bin/rtlcss.js" + }, + "engines": { + "node": ">=12.0.0" } }, "node_modules/rtlcss/node_modules/postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/rtlcss/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "node": "^10 || ^12 || >=14" } }, "node_modules/run-async": { @@ -21901,12 +22976,13 @@ } }, "node_modules/sass": { - "version": "1.72.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.72.0.tgz", - "integrity": "sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==", + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.81.0.tgz", + "integrity": "sha512-Q4fOxRfhmv3sqCLoGfvrC9pRV8btc0UtqL9mN6Yrv6Qi9ScL55CVH1vlPP863ISLEEMNLLuu9P+enCeGHlnzhA==", + "license": "MIT", "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -21914,12 +22990,16 @@ }, "engines": { "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, "node_modules/sass-loader": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.1.1.tgz", - "integrity": "sha512-QX8AasDg75monlybel38BZ49JP5Z+uSKfKwF2rO7S74BywaRmGQMUBw9dtkS+ekyM/QnP+NOrRYq8ABMZ9G8jw==", + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.3.tgz", + "integrity": "sha512-gosNorT1RCkuCMyihv6FBRR7BMV06oKRAs+l4UMp1mlcVg9rWN6KMmUj3igjQwmYys4mDP3etEYJgiHRbgHCHA==", + "license": "MIT", "dependencies": { "neo-async": "^2.6.2" }, @@ -21955,93 +23035,32 @@ } } }, - "node_modules/sass/node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sass/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/sass/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/sass/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sass/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/sass/node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" } }, "node_modules/sass/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "license": "MIT", "engines": { - "node": ">=8.10.0" + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/sax": { @@ -22124,33 +23143,51 @@ "integrity": "sha512-qGVDoreyYiP1pkQnbnFAUIS5AjenNwwQBdl7zeos9etl+hYKWahjRTfzAZZYBv5xNHx7vNKCmaLDQZ6Fr2AEXg==" }, "node_modules/selenium-webdriver": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz", - "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.26.0.tgz", + "integrity": "sha512-nA7jMRIPV17mJmAiTDBWN96Sy0Uxrz5CCLb7bLVV6PpL417SyBMPc2Zo/uoREc2EOHlzHwHwAlFtgmSngSY4WQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "jszip": "^3.1.3", - "rimraf": "^2.5.4", - "tmp": "0.0.30", - "xml2js": "^0.4.17" + "@bazel/runfiles": "^6.3.1", + "jszip": "^3.10.1", + "tmp": "^0.2.3", + "ws": "^8.18.0" }, "engines": { - "node": ">= 6.9.0" + "node": ">= 14.21.0" } }, - "node_modules/selenium-webdriver/node_modules/rimraf": { - "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", + "node_modules/selenium-webdriver/node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/selenium-webdriver/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" }, - "bin": { - "rimraf": "bin.js" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/semver": { @@ -22256,7 +23293,8 @@ "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true }, "node_modules/setprototypeof": { "version": "1.2.0", @@ -22768,9 +23806,10 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -22865,6 +23904,7 @@ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, + "license": "MIT", "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -22889,7 +23929,8 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ssri": { "version": "10.0.5", @@ -22973,6 +24014,7 @@ "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", "dev": true, + "license": "ISC", "engines": { "node": ">=0.10.0" } @@ -23217,25 +24259,10 @@ "node": ">=6" } }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "peer": true, - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "peer": true, "engines": { "node": ">=8" }, @@ -23244,47 +24271,19 @@ } }, "node_modules/style-loader": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.18.2.tgz", - "integrity": "sha512-WPpJPZGUxWYHWIUMNNOYqql7zh85zGmr84FdTVWq52WTIkqlW9xSxD3QYWi/T31cqn9UNSsietVEgGn2aaSCzw==", - "dependencies": { - "loader-utils": "^1.0.2", - "schema-utils": "^0.3.0" - }, - "engines": { - "node": ">= 0.12.0" - } - }, - "node_modules/style-loader/node_modules/ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==", - "dependencies": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "node_modules/style-loader/node_modules/fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha512-fueX787WZKCV0Is4/T2cyAdM4+x1S3MXXOAhavE1ys/W42SHAPacLTQhucja22QBYrfGw50M2sRiXPtTGv9Ymw==" - }, - "node_modules/style-loader/node_modules/json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha512-4JD/Ivzg7PoW8NzdrBSr3UFwC9mHgvI7Z6z3QGBsSHgKaRTUDmyZAAKJo2UbG1kUVfS9WS8bi36N49U1xw43DA==" - }, - "node_modules/style-loader/node_modules/schema-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", - "integrity": "sha512-QaVYBaD9U8scJw2EBWnCBY+LJ0AD+/2edTaigDs0XLDLBfJmSUK9KGqktg1rb32U3z4j/XwvFwHHH1YfbYFd7Q==", - "dependencies": { - "ajv": "^5.0.0" - }, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "license": "MIT", "engines": { - "node": ">= 4.3 < 5.0.0 || >= 5.10" + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" } }, "node_modules/style-search": { @@ -23294,126 +24293,133 @@ "dev": true }, "node_modules/stylelint": { - "version": "14.16.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.16.1.tgz", - "integrity": "sha512-ErlzR/T3hhbV+a925/gbfc3f3Fep9/bnspMiJPorfGEmcBbXdS+oo6LrVtoUZ/w9fqD6o6k7PtUlCOsCRdjX/A==", + "version": "16.10.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.10.0.tgz", + "integrity": "sha512-z/8X2rZ52dt2c0stVwI9QL2AFJhLhbPkyfpDFcizs200V/g7v+UYY6SNcB9hKOLcDDX/yGLDsY/pX08sLkz9xQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", "peer": true, "dependencies": { - "@csstools/selector-specificity": "^2.0.2", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", + "@csstools/media-query-list-parser": "^3.0.1", + "@csstools/selector-specificity": "^4.0.0", + "@dual-bundle/import-meta-resolve": "^4.1.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", - "cosmiconfig": "^7.1.0", - "css-functions-list": "^3.1.0", - "debug": "^4.3.4", - "fast-glob": "^3.2.12", + "cosmiconfig": "^9.0.0", + "css-functions-list": "^3.2.3", + "css-tree": "^3.0.0", + "debug": "^4.3.7", + "fast-glob": "^3.3.2", "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^9.1.0", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", - "html-tags": "^3.2.0", - "ignore": "^5.2.1", - "import-lazy": "^4.0.0", + "html-tags": "^3.3.1", + "ignore": "^6.0.2", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.26.0", + "known-css-properties": "^0.34.0", "mathml-tag-names": "^2.1.3", - "meow": "^9.0.0", - "micromatch": "^4.0.5", + "meow": "^13.2.0", + "micromatch": "^4.0.8", "normalize-path": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.19", - "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.11", + "picocolors": "^1.0.1", + "postcss": "^8.4.47", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^6.1.2", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "style-search": "^0.1.0", - "supports-hyperlinks": "^2.3.0", + "supports-hyperlinks": "^3.1.0", "svg-tags": "^1.0.0", - "table": "^6.8.1", - "v8-compile-cache": "^2.3.0", - "write-file-atomic": "^4.0.2" + "table": "^6.8.2", + "write-file-atomic": "^5.0.1" }, "bin": { - "stylelint": "bin/stylelint.js" + "stylelint": "bin/stylelint.mjs" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" + "node": ">=18.12.0" } }, "node_modules/stylelint-formatter-pretty": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/stylelint-formatter-pretty/-/stylelint-formatter-pretty-1.1.4.tgz", - "integrity": "sha512-wgPu7UaWJO8ISiSXelp46jhtM8J/CIsrndgpUOow80v0GvyrmLrRURCFV16oKddkH0NcjlO91C7c+ef2U+siNw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/stylelint-formatter-pretty/-/stylelint-formatter-pretty-4.0.1.tgz", + "integrity": "sha512-39nasPN8QNdMBq2eghI/SwpyVKx8kJGtmE/KJ5oFvCg0ZvZ5qjJbQgEulzC0gT70XZ+iRk7p1W5tE9IBQhFjPw==", "dev": true, + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz/" + } + ], "license": "MIT", "dependencies": { - "ansi-escapes": "^4.3.0", - "chalk": "^3.0.0", - "log-symbols": "^3.0.0", - "plur": "^3.1.1", - "string-width": "^4.2.0" + "ansi-escapes": "^7.0.0", + "log-symbols": "^7.0.0", + "picocolors": "^1.0.0", + "plur": "^5.1.0", + "string-width": "^7.0.0", + "supports-hyperlinks": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": ">=16.0.0" } }, - "node_modules/stylelint-formatter-pretty/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/stylelint-formatter-pretty/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "environment": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stylelint-formatter-pretty/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "node_modules/stylelint-formatter-pretty/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=8" - } - }, - "node_modules/stylelint-formatter-pretty/node_modules/color-convert": { - "version": "2.0.1", - "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" + "node": ">=12" }, - "engines": { - "node": ">=7.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/stylelint-formatter-pretty/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/stylelint-formatter-pretty/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", "dev": true, "license": "MIT" }, @@ -23427,6 +24433,40 @@ "node": ">=8" } }, + "node_modules/stylelint-formatter-pretty/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint-formatter-pretty/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/stylelint-formatter-pretty/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -23440,55 +24480,115 @@ "node": ">=8" } }, - "node_modules/stylelint-scss": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.7.0.tgz", - "integrity": "sha512-TSUgIeS0H3jqDZnby1UO1Qv3poi1N8wUYIJY6D1tuUq2MN3lwp/rITVo0wD+1SWTmRm0tNmGO0b7nKInnqF6Hg==", + "node_modules/stylelint-formatter-pretty/node_modules/supports-hyperlinks": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz", + "integrity": "sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A==", "dev": true, + "license": "MIT", "dependencies": { - "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0" + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" }, - "peerDependencies": { - "stylelint": "^14.5.1 || ^15.0.0" + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stylelint-scss/node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "node_modules/stylelint/node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/stylelint/node_modules/@csstools/css-tokenizer": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, "engines": { - "node": ">=4" + "node": ">=18" } }, - "node_modules/stylelint-scss/node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "node_modules/stylelint/node_modules/@csstools/media-query-list-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz", + "integrity": "sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1" + } }, "node_modules/stylelint/node_modules/@csstools/selector-specificity": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", - "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-4.0.0.tgz", + "integrity": "sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "peer": true, "engines": { - "node": "^14 || ^16 || >=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "node": ">=18" }, "peerDependencies": { - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^6.1.0" } }, "node_modules/stylelint/node_modules/balanced-match": { @@ -23498,10 +24598,84 @@ "dev": true, "peer": true }, - "node_modules/stylelint/node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "node_modules/stylelint/node_modules/css-tree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.0.1.tgz", + "integrity": "sha512-8Fxxv+tGhORlshCdCwnNJytvlvq46sOLSYEx2ZIGurahWvMucSRnyjPA3AmrMq4VPRYbHVpWj5VkiVasrM2H4Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mdn-data": "2.12.1", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.1.0.tgz", + "integrity": "sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/stylelint/node_modules/flat-cache": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-5.0.0.tgz", + "integrity": "sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.3.1", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/stylelint/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", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/ignore": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", + "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint/node_modules/mdn-data": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.1.tgz", + "integrity": "sha512-rsfnCbOHjqrhWxwt5/wtSLzpoKTzW7OXdT5lLOIH1OTYhWu9rRJveGq0sKvDZODABH7RX+uoR+DYcpFnq4Tf6Q==", + "dev": true, + "license": "CC0-1.0", + "peer": true + }, + "node_modules/stylelint/node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "dev": true, "funding": [ { @@ -23517,38 +24691,51 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "peer": true, "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/stylelint/node_modules/postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "peer": true, "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "node": ">=18.0" }, "peerDependencies": { - "postcss": "^8.3.3" + "postcss": "^8.4.31" } }, "node_modules/stylelint/node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "cssesc": "^3.0.0", @@ -23565,18 +24752,65 @@ "dev": true, "peer": true }, + "node_modules/stylelint/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/stylelint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/supports-hyperlinks": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz", + "integrity": "sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/stylelint/node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, + "license": "ISC", "peer": true, "dependencies": { "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/supports-color": { @@ -24055,14 +25289,6 @@ "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, "node_modules/to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -24149,14 +25375,6 @@ "node": ">=6" } }, - "node_modules/tough-cookie/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "engines": { - "node": ">=6" - } - }, "node_modules/tr46": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", @@ -24168,24 +25386,6 @@ "node": ">=8" } }, - "node_modules/tr46/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -24227,11 +25427,37 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD", + "peer": true + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -24243,7 +25469,8 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true + "dev": true, + "license": "Unlicense" }, "node_modules/type": { "version": "2.7.2", @@ -24380,6 +25607,21 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/typhonjs-ast-walker": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/typhonjs-ast-walker/-/typhonjs-ast-walker-0.1.1.tgz", @@ -24435,38 +25677,11 @@ "integrity": "sha512-2qReiiM1W8dVZakdAhZuEVxQvr+/MlqWGFkPckpDT733tUgYRQmJMvjulXs6cnmpMUqc+d1OVaYbnInZ4tzC7A==", "dev": true }, - "node_modules/ua-parser-js": { - "version": "0.7.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.37.tgz", - "integrity": "sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "engines": { - "node": "*" - } - }, "node_modules/uglify-js": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.7.0.tgz", - "integrity": "sha512-0casKM/s/IaTnQNkRe4iqNdRG2BvHEeJvmaB+g19VE50I4zxJ3E3+DdTWc7hyrR9VScuMDGAaQm3NLSqTu8Bmw==", - "dependencies": { - "async": "~0.2.6", - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" - }, + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", "bin": { "uglifyjs": "bin/uglifyjs" }, @@ -24474,61 +25689,6 @@ "node": ">=0.8.0" } }, - "node_modules/uglify-js/node_modules/async": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", - "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==" - }, - "node_modules/uglify-js/node_modules/camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uglify-js/node_modules/cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA==", - "dependencies": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - } - }, - "node_modules/uglify-js/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/uglify-js/node_modules/wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/uglify-js/node_modules/yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A==", - "dependencies": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } - }, - "node_modules/uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha512-vb2s1lYx2xBtUgy+ta+b2J/GLVUR+wmpINwHePmPRhOsIVCG2wDzKJ0n14GslH1BifsqVzSOwQhRaCAsZ/nI4Q==" - }, "node_modules/ultron": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", @@ -24564,9 +25724,9 @@ } }, "node_modules/underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", "license": "MIT" }, "node_modules/underscore.string": { @@ -24592,9 +25752,10 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", "engines": { "node": ">=4" } @@ -24603,6 +25764,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -24612,9 +25774,10 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "license": "MIT", "engines": { "node": ">=4" } @@ -24623,6 +25786,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "license": "MIT", "engines": { "node": ">=4" } @@ -24772,9 +25936,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "funding": [ { "type": "opencollective", @@ -24791,8 +25955,8 @@ ], "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -24809,14 +25973,6 @@ "punycode": "^2.1.0" } }, - "node_modules/uri-js/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "engines": { - "node": ">=6" - } - }, "node_modules/urijs": { "version": "1.19.11", "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", @@ -24839,11 +25995,6 @@ "requires-port": "^1.0.0" } }, - "node_modules/url-toolkit": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz", - "integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==" - }, "node_modules/use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -24945,6 +26096,25 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/util.promisify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.1.2.tgz", + "integrity": "sha512-PBdZ03m1kBnQ5cjjO0ZvJMJS+QsbyIcFwi4hY4U76OQsCO9JrOYjbCFgIF76ccFg9xnJo7ZHPkqyj1GqmdS7MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "object.getownpropertydescriptors": "^2.1.6", + "safe-array-concat": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/util/node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", @@ -24969,13 +26139,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/v8-compile-cache": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", - "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", - "dev": true, - "peer": true - }, "node_modules/v8-to-istanbul": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", @@ -25031,6 +26194,7 @@ "engines": [ "node >=0.6.0" ], + "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -25041,7 +26205,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/void-elements": { "version": "2.0.1", @@ -25110,18 +26275,18 @@ } }, "node_modules/webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "version": "5.96.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", + "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.5", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", @@ -25262,11 +26427,12 @@ } }, "node_modules/webpack-merge": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.1.tgz", - "integrity": "sha512-geQsZ86YkXOVOjvPC5yv3JSNnL6/X3Kzh935AQ/gJNEYXEfJDQFu/sdFuktS9OW2JcH/SJec8TGfRdrpHshH7A==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", + "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "license": "MIT", "dependencies": { - "lodash": "^4.17.4" + "lodash": "^4.17.15" } }, "node_modules/webpack-sources": { @@ -25500,14 +26666,6 @@ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true }, - "node_modules/window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha512-1pTPQDKTdd61ozlKGNCjhNRd+KPmgLSGa3mZTHoOliaGcESD8G1PXhh7c1fgiPjVbNVfgy2Faw4BI8/m0cC8Mg==", - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -25677,28 +26835,6 @@ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/xmlbuilder": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", @@ -25742,16 +26878,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -25813,6 +26939,19 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 1f48500e6e27..f3d3901d3a67 100644 --- a/package.json +++ b/package.json @@ -15,11 +15,11 @@ "watch-sass": "scripts/watch_sass.sh" }, "dependencies": { - "@babel/core": "7.25.2", + "@babel/core": "7.26.0", "@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.24.7", + "@babel/preset-react": "7.25.9", "@edx/brand-edx.org": "^2.0.7", "@edx/edx-bootstrap": "1.0.4", "@edx/edx-proctoring": "^4.18.1", @@ -34,69 +34,69 @@ "backbone.paginator": "2.0.8", "bootstrap": "4.0.0", "camelize": "1.0.1", - "classnames": "2.3.1", + "classnames": "2.5.1", "css-loader": "0.28.8", "datatables": "1.10.18", - "datatables.net-fixedcolumns": "3.2.6", + "datatables.net-fixedcolumns": "5.0.4", "edx-proctoring-proctortrack": "git+https://git@github.com/anupdhabarde/edx-proctoring-proctortrack.git#f0fa9edbd16aa5af5a41ac309d2609e529ea8732", - "edx-ui-toolkit": "1.5.5", + "edx-ui-toolkit": "1.8.6", "exports-loader": "0.6.4", "file-loader": "^6.2.0", "font-awesome": "4.7.0", - "hls.js": "0.14.17", - "imports-loader": "0.7.1", + "hls.js": "1.5.17", + "imports-loader": "0.8.0", "jest-environment-jsdom": "^26.0.0", "jquery": "2.2.4", "jquery-migrate": "1.4.1", "jquery.scrollto": "2.1.3", "js-cookie": "3.0.5", "moment": "2.30.1", - "moment-timezone": "0.5.45", - "node-gyp": "10.0.1", + "moment-timezone": "0.5.46", + "node-gyp": "10.2.0", "picturefill": "3.0.3", "popper.js": "1.16.1", - "prop-types": "15.6.0", - "raw-loader": "0.5.1", + "prop-types": "15.8.1", + "raw-loader": "4.0.2", "react": "16.14.0", "react-dom": "16.14.0", "react-focus-lock": "^1.19.1", - "react-redux": "5.0.7", + "react-redux": "5.1.2", "react-router-dom": "5.1.2", - "react-slick": "0.29.0", + "react-slick": "0.30.2", "redux": "3.7.2", "redux-thunk": "2.2.0", "requirejs": "2.3.7", - "rtlcss": "2.6.2", + "rtlcss": "4.3.0", "sass": "^1.54.8", - "sass-loader": "^14.1.1", + "sass-loader": "^16.0.0", "scriptjs": "2.5.9", - "style-loader": "0.18.2", + "style-loader": "4.0.0", "svg-inline-loader": "0.8.2", - "uglify-js": "2.7.0", - "underscore": "1.13.1", + "uglify-js": "3.19.3", + "underscore": "1.13.7", "underscore.string": "3.3.6", "webpack": "^5.90.3", "webpack-bundle-tracker": "0.4.3", - "webpack-merge": "4.1.1", + "webpack-merge": "4.2.2", "whatwg-fetch": "2.0.4", "which-country": "1.0.0" }, "devDependencies": { - "@edx/eslint-config": "^3.1.1", - "@edx/mockprock": "github:openedx/mockprock#3ad18c6888e6521e9bf7a4df0db6f8579b928235", + "@edx/eslint-config": "^4.0.0", + "@edx/mockprock": "github:openedx/mockprock#d70b05231bd46b0122616c24e209c890ef2633c0", "@edx/stylelint-config-edx": "2.3.3", "babel-jest": "26.6.3", "enzyme": "3.11.0", "enzyme-adapter-react-16": "1.15.8", - "eslint-import-resolver-webpack": "0.13.8", + "eslint-import-resolver-webpack": "0.13.9", "jasmine-core": "2.6.4", "jasmine-jquery": "git+https://git@github.com/velesin/jasmine-jquery.git#ebad463d592d3fea00c69f26ea18a930e09c7b58", "jest": "26.6.3", - "jest-enzyme": "6.1.2", + "jest-enzyme": "7.1.2", "karma": "0.13.22", "karma-chrome-launcher": "0.2.3", "karma-coverage": "0.5.5", - "karma-firefox-launcher": "0.1.7", + "karma-firefox-launcher": "2.1.3", "karma-jasmine": "0.3.8", "karma-jasmine-html-reporter": "0.2.2", "karma-junit-reporter": "1.2.0", @@ -107,11 +107,11 @@ "karma-webpack": "^5.0.1", "plato": "1.7.0", "react-test-renderer": "16.14.0", - "selenium-webdriver": "3.6.0", + "selenium-webdriver": "4.26.0", "sinon": "2.4.1", "squirejs": "0.1.0", "string-replace-loader": "^3.1.0", - "stylelint-formatter-pretty": "1.1.4", + "stylelint-formatter-pretty": "4.0.1", "webpack-cli": "^5.1.4" } } diff --git a/pavelib/paver_tests/test_timer.py b/pavelib/paver_tests/test_timer.py index 5ccbf74abcf9..bc9817668347 100644 --- a/pavelib/paver_tests/test_timer.py +++ b/pavelib/paver_tests/test_timer.py @@ -77,17 +77,9 @@ def test_times(self): messages = self.get_log_messages() assert len(messages) == 1 - # I'm not using assertDictContainsSubset because it is - # removed in python 3.2 (because the arguments were backwards) - # and it wasn't ever replaced by anything *headdesk* - assert 'duration' in messages[0] - assert 35.6 == messages[0]['duration'] - - assert 'started_at' in messages[0] - assert start.isoformat(' ') == messages[0]['started_at'] - - assert 'ended_at' in messages[0] - assert end.isoformat(' ') == messages[0]['ended_at'] + assert 'duration' in messages[0] and messages[0]['duration'] == 35.6 + assert 'started_at' in messages[0] and messages[0]['started_at'] == start.isoformat(' ') + assert 'ended_at' in messages[0] and messages[0]['ended_at'] == end.isoformat(' ') @patch.object(timer, 'PAVER_TIMER_LOG', None) def test_no_logs(self): @@ -99,28 +91,18 @@ def test_arguments(self): messages = self.get_log_messages(args=(1, 'foo'), kwargs=dict(bar='baz')) assert len(messages) == 1 - # I'm not using assertDictContainsSubset because it is - # removed in python 3.2 (because the arguments were backwards) - # and it wasn't ever replaced by anything *headdesk* - assert 'args' in messages[0] - assert [repr(1), repr('foo')] == messages[0]['args'] - assert 'kwargs' in messages[0] - assert {'bar': repr('baz')} == messages[0]['kwargs'] + assert 'args' in messages[0] and messages[0]['args'] == [repr(1), repr('foo')] + assert 'kwargs' in messages[0] and messages[0]['kwargs'] == {'bar': repr('baz')} @patch.object(timer, 'PAVER_TIMER_LOG', '/tmp/some-log') def test_task_name(self): messages = self.get_log_messages() assert len(messages) == 1 - # I'm not using assertDictContainsSubset because it is - # removed in python 3.2 (because the arguments were backwards) - # and it wasn't ever replaced by anything *headdesk* - assert 'task' in messages[0] - assert 'pavelib.paver_tests.test_timer.identity' == messages[0]['task'] + assert 'task' in messages[0] and messages[0]['task'] == 'pavelib.paver_tests.test_timer.identity' @patch.object(timer, 'PAVER_TIMER_LOG', '/tmp/some-log') def test_exceptions(self): - @timer.timed def raises(): """ @@ -131,11 +113,7 @@ def raises(): messages = self.get_log_messages(task=raises, raises=Exception) assert len(messages) == 1 - # I'm not using assertDictContainsSubset because it is - # removed in python 3.2 (because the arguments were backwards) - # and it wasn't ever replaced by anything *headdesk* - assert 'exception' in messages[0] - assert 'Exception: The Message!' == messages[0]['exception'] + assert 'exception' in messages[0] and messages[0]['exception'] == 'Exception: The Message!' @patch.object(timer, 'PAVER_TIMER_LOG', '/tmp/some-log-%Y-%m-%d-%H-%M-%S.log') def test_date_formatting(self): diff --git a/requirements/common_constraints.txt b/requirements/common_constraints.txt index 5188f37250ef..b8166ba67540 100644 --- a/requirements/common_constraints.txt +++ b/requirements/common_constraints.txt @@ -15,7 +15,6 @@ # Note: Changes to this file will automatically be used by other repos, referencing # this file from Github directly. It does not require packaging in edx-lint. - # using LTS django version Django<5.0 @@ -26,13 +25,6 @@ elasticsearch<7.14.0 # django-simple-history>3.0.0 adds indexing and causes a lot of migrations to be affected -# 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 -# This can be unpinned once https://github.com/openedx/edx-platform/issues/34586 -# has been resolved and edx-platform is running with pymongo>=4.4.0 - - # Cause: https://github.com/openedx/edx-lint/issues/458 # This can be unpinned once https://github.com/openedx/edx-lint/issues/459 has been resolved. pip<24.3 diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 4bf073dfab05..21dc4cb0f882 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -19,10 +19,6 @@ # Ticket: https://github.com/openedx/edx-platform/issues/35334 algoliasearch<4.0.0 -# Date: 2024-03-14 -# Temporary to Support the python 3.11 Upgrade -# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35281 -backports.zoneinfo;python_version<"3.9" # Newer versions have zoneinfo available in the standard library # Date: 2020-02-26 # As it is not clarified what exact breaking changes will be introduced as per @@ -82,7 +78,7 @@ django-storages<1.14.4 # 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.30.1 +edx-enterprise==4.33.0 # Date: 2024-05-09 # This has to be constrained as well because newer versions of edx-i18n-tools need the @@ -105,13 +101,6 @@ event-tracking==3.0.0 # https://github.com/openedx/edx-platform/issues/31616 libsass==0.10.0 -# Date: 2024-04-30 -# lxml>=5.0 introduced breaking changes related to system dependencies -# lxml==5.2.1 introduced new extra so we'll nee to rename lxml --> lxml[html-clean] -# This constraint can be removed once we upgrade to Python 3.11 -# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35272 -lxml<5.0 - # Date: 2018-12-14 # markdown>=3.4.0 has failures due to internal refactorings which causes the tests to fail # pinning the version untill the issue gets resolved in the package itself @@ -146,7 +135,7 @@ optimizely-sdk<5.0 # Date: 2023-09-18 # pinning this version to avoid updates while the library is being developed # Issue for unpinning: https://github.com/openedx/edx-platform/issues/35269 -openedx-learning==0.16.1 +openedx-learning==0.18.0 # Date: 2023-11-29 # Open AI version 1.0.0 dropped support for openai.ChatCompletion which is currently in use in enterprise. @@ -167,6 +156,7 @@ pycodestyle<2.9.0 # Date: 2021-07-12 # Issue for unpinning: https://github.com/openedx/edx-platform/issues/33560 pylint<2.16.0 # greater version failing quality test. Fix them in seperate ticket. +astroid<2.14.0 # Date: 2021-08-25 # At the time of writing this comment, we do not know whether py2neo>=2022 @@ -191,8 +181,3 @@ social-auth-app-django<=5.4.1 # # Date: 2024-10-14 # # The edx-enterprise is currently using edx-rest-api-client==5.7.1, which needs to be updated first. # edx-rest-api-client==5.7.1 - -# Date: 2024-04-24 -# xmlsec==1.3.14 breaking tests or all builds, can be removed once a fix is available -# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35264 -xmlsec<1.3.14 diff --git a/requirements/edx-sandbox/base.in b/requirements/edx-sandbox/base.in index 4c1a61bc2723..0c331ce95d62 100644 --- a/requirements/edx-sandbox/base.in +++ b/requirements/edx-sandbox/base.in @@ -2,7 +2,7 @@ chem # A helper library for chemistry calculations cryptography # Implementations of assorted cryptography algorithms -lxml # XML parser +lxml[html_clean] # XML parser matplotlib # 2D plotting library networkx # Utilities for creating, manipulating, and studying network graphs nltk # Natural language processing; used by the chem package diff --git a/requirements/edx-sandbox/base.txt b/requirements/edx-sandbox/base.txt index eb74898596ff..8cf0cbb82904 100644 --- a/requirements/edx-sandbox/base.txt +++ b/requirements/edx-sandbox/base.txt @@ -26,11 +26,13 @@ joblib==1.4.2 # via nltk kiwisolver==1.4.7 # via matplotlib -lxml==4.9.4 +lxml[html-clean,html_clean]==5.3.0 # via - # -c requirements/edx-sandbox/../constraints.txt # -r requirements/edx-sandbox/base.in + # lxml-html-clean # openedx-calc +lxml-html-clean==0.3.1 + # via lxml markupsafe==3.0.2 # via # chem diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index 88ab7b1c16a5..c697f8eb81fb 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -427,7 +427,7 @@ edx-celeryutils==1.3.0 # -r requirements/edx/kernel.in # edx-name-affirmation # super-csv -edx-codejail==3.5.1 +edx-codejail==3.5.2 # via -r requirements/edx/kernel.in edx-completion==4.7.3 # via -r requirements/edx/kernel.in @@ -467,7 +467,7 @@ edx-drf-extensions==10.5.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.30.1 +edx-enterprise==4.33.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in @@ -502,7 +502,7 @@ edx-opaque-keys[django]==2.11.0 # ora2 edx-organizations==6.13.0 # via -r requirements/edx/kernel.in -edx-proctoring==4.18.2 +edx-proctoring==4.18.3 # via # -r requirements/edx/kernel.in # edx-proctoring-proctortrack @@ -513,7 +513,7 @@ edx-rest-api-client==6.0.0 # -r requirements/edx/kernel.in # edx-enterprise # edx-proctoring -edx-search==4.1.0 +edx-search==4.1.1 # via -r requirements/edx/kernel.in edx-sga==0.25.0 # via -r requirements/edx/bundled.in @@ -708,19 +708,21 @@ loremipsum==1.0.5 # via ora2 lti-consumer-xblock==9.11.3 # via -r requirements/edx/kernel.in -lxml==4.9.4 +lxml[html-clean,html_clean]==5.3.0 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in # edx-i18n-tools # edxval # lti-consumer-xblock + # lxml-html-clean # olxcleaner # openedx-calc # ora2 # python3-saml # xblock # xmlsec +lxml-html-clean==0.3.1 + # via lxml mailsnake==1.6.4 # via -r requirements/edx/bundled.in mako==1.3.6 @@ -825,7 +827,7 @@ openedx-filters==1.11.0 # -r requirements/edx/kernel.in # lti-consumer-xblock # ora2 -openedx-learning==0.16.1 +openedx-learning==0.18.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in @@ -835,7 +837,7 @@ optimizely-sdk==4.1.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/bundled.in -ora2==6.12.2 +ora2==6.14.0 # via -r requirements/edx/bundled.in packaging==24.1 # via @@ -1285,10 +1287,8 @@ xblock-utils==4.0.0 # via # edx-sga # xblock-poll -xmlsec==1.3.13 - # via - # -c requirements/edx/../constraints.txt - # python3-saml +xmlsec==1.3.14 + # via python3-saml xss-utils==0.6.0 # via -r requirements/edx/kernel.in yarl==1.17.0 diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index 328dfe81381d..a07b24914d2d 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -83,9 +83,12 @@ asn1crypto==1.5.1 # snowflake-connector-python astroid==2.13.5 # via + # -c requirements/edx/../constraints.txt + # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # pylint # pylint-celery + # sphinx-autoapi attrs==24.2.0 # via # -r requirements/edx/doc.txt @@ -692,7 +695,7 @@ edx-celeryutils==1.3.0 # -r requirements/edx/testing.txt # edx-name-affirmation # super-csv -edx-codejail==3.5.1 +edx-codejail==3.5.2 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -741,7 +744,7 @@ edx-drf-extensions==10.5.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.30.1 +edx-enterprise==4.33.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt @@ -790,7 +793,7 @@ edx-organizations==6.13.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -edx-proctoring==4.18.2 +edx-proctoring==4.18.3 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -806,7 +809,7 @@ edx-rest-api-client==6.0.0 # -r requirements/edx/testing.txt # edx-enterprise # edx-proctoring -edx-search==4.1.0 +edx-search==4.1.1 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1103,6 +1106,7 @@ jinja2==3.1.4 # code-annotations # diff-cover # sphinx + # sphinx-autoapi jmespath==1.0.1 # via # -r requirements/edx/doc.txt @@ -1166,6 +1170,7 @@ lazy==1.6 # xblock lazy-object-proxy==1.10.0 # via + # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # astroid libsass==0.10.0 @@ -1183,14 +1188,14 @@ lti-consumer-xblock==9.11.3 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -lxml==4.9.4 +lxml[html-clean]==5.3.0 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-i18n-tools # edxval # lti-consumer-xblock + # lxml-html-clean # olxcleaner # openedx-calc # ora2 @@ -1198,6 +1203,11 @@ lxml==4.9.4 # python3-saml # xblock # xmlsec +lxml-html-clean==0.3.1 + # via + # -r requirements/edx/doc.txt + # -r requirements/edx/testing.txt + # lxml mailsnake==1.6.4 # via # -r requirements/edx/doc.txt @@ -1376,7 +1386,7 @@ openedx-filters==1.11.0 # -r requirements/edx/testing.txt # lti-consumer-xblock # ora2 -openedx-learning==0.16.1 +openedx-learning==0.18.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt @@ -1390,7 +1400,7 @@ optimizely-sdk==4.1.1 # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -ora2==6.12.2 +ora2==6.14.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt @@ -1794,6 +1804,7 @@ pyyaml==6.0.2 # edx-django-release-util # edx-i18n-tools # jsondiff + # sphinx-autoapi # sphinxcontrib-openapi # xblock random2==1.0.2 @@ -1994,6 +2005,7 @@ sphinx==8.1.3 # via # -r requirements/edx/doc.txt # pydata-sphinx-theme + # sphinx-autoapi # sphinx-book-theme # sphinx-design # sphinx-mdinclude @@ -2001,6 +2013,8 @@ sphinx==8.1.3 # sphinxcontrib-httpdomain # sphinxcontrib-openapi # sphinxext-rediraffe +sphinx-autoapi==3.3.3 + # via -r requirements/edx/doc.txt sphinx-book-theme==1.1.3 # via -r requirements/edx/doc.txt sphinx-design==0.6.1 @@ -2270,9 +2284,8 @@ xblock-utils==4.0.0 # -r requirements/edx/testing.txt # edx-sga # xblock-poll -xmlsec==1.3.13 +xmlsec==1.3.14 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # python3-saml diff --git a/requirements/edx/doc.in b/requirements/edx/doc.in index 013bafc42ec0..78afbf9d6bd0 100644 --- a/requirements/edx/doc.in +++ b/requirements/edx/doc.in @@ -10,3 +10,4 @@ sphinx-design # provides various responsive web-components sphinxcontrib-openapi[markdown] # Be able to render openapi schema in a sphinx project sphinxext-rediraffe # Quickly and easily redirect when we move pages around. sphinx-reredirects # Redirect from a sphinx project out to other places on the web including other sphinx projects +sphinx-autoapi diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index fc353debc489..fb3d610ded6e 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -57,6 +57,10 @@ asn1crypto==1.5.1 # via # -r requirements/edx/base.txt # snowflake-connector-python +astroid==2.13.5 + # via + # -c requirements/edx/../constraints.txt + # sphinx-autoapi attrs==24.2.0 # via # -r requirements/edx/base.txt @@ -507,7 +511,7 @@ edx-celeryutils==1.3.0 # -r requirements/edx/base.txt # edx-name-affirmation # super-csv -edx-codejail==3.5.1 +edx-codejail==3.5.2 # via -r requirements/edx/base.txt edx-completion==4.7.3 # via -r requirements/edx/base.txt @@ -547,7 +551,7 @@ edx-drf-extensions==10.5.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.30.1 +edx-enterprise==4.33.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -581,7 +585,7 @@ edx-opaque-keys[django]==2.11.0 # ora2 edx-organizations==6.13.0 # via -r requirements/edx/base.txt -edx-proctoring==4.18.2 +edx-proctoring==4.18.3 # via # -r requirements/edx/base.txt # edx-proctoring-proctortrack @@ -594,7 +598,7 @@ edx-rest-api-client==6.0.0 # -r requirements/edx/base.txt # edx-enterprise # edx-proctoring -edx-search==4.1.0 +edx-search==4.1.1 # via -r requirements/edx/base.txt edx-sga==0.25.0 # via -r requirements/edx/base.txt @@ -790,6 +794,7 @@ jinja2==3.1.4 # -r requirements/edx/base.txt # code-annotations # sphinx + # sphinx-autoapi jmespath==1.0.1 # via # -r requirements/edx/base.txt @@ -840,6 +845,8 @@ lazy==1.6 # lti-consumer-xblock # ora2 # xblock +lazy-object-proxy==1.10.0 + # via astroid libsass==0.10.0 # via # -c requirements/edx/../constraints.txt @@ -850,19 +857,23 @@ loremipsum==1.0.5 # ora2 lti-consumer-xblock==9.11.3 # via -r requirements/edx/base.txt -lxml==4.9.4 +lxml[html-clean]==5.3.0 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-i18n-tools # edxval # lti-consumer-xblock + # lxml-html-clean # olxcleaner # openedx-calc # ora2 # python3-saml # xblock # xmlsec +lxml-html-clean==0.3.1 + # via + # -r requirements/edx/base.txt + # lxml mailsnake==1.6.4 # via -r requirements/edx/base.txt mako==1.3.6 @@ -986,7 +997,7 @@ openedx-filters==1.11.0 # -r requirements/edx/base.txt # lti-consumer-xblock # ora2 -openedx-learning==0.16.1 +openedx-learning==0.18.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -996,7 +1007,7 @@ optimizely-sdk==4.1.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt -ora2==6.12.2 +ora2==6.14.0 # via -r requirements/edx/base.txt packaging==24.1 # via @@ -1243,6 +1254,7 @@ pyyaml==6.0.2 # edx-django-release-util # edx-i18n-tools # jsondiff + # sphinx-autoapi # sphinxcontrib-openapi # xblock random2==1.0.2 @@ -1401,6 +1413,7 @@ sphinx==8.1.3 # via # -r requirements/edx/doc.in # pydata-sphinx-theme + # sphinx-autoapi # sphinx-book-theme # sphinx-design # sphinx-mdinclude @@ -1408,6 +1421,8 @@ sphinx==8.1.3 # sphinxcontrib-httpdomain # sphinxcontrib-openapi # sphinxext-rediraffe +sphinx-autoapi==3.3.3 + # via -r requirements/edx/doc.in sphinx-book-theme==1.1.3 # via -r requirements/edx/doc.in sphinx-design==0.6.1 @@ -1552,7 +1567,9 @@ webob==1.8.9 # -r requirements/edx/base.txt # xblock wrapt==1.16.0 - # via -r requirements/edx/base.txt + # via + # -r requirements/edx/base.txt + # astroid xblock[django]==5.1.0 # via # -r requirements/edx/base.txt @@ -1579,9 +1596,8 @@ xblock-utils==4.0.0 # -r requirements/edx/base.txt # edx-sga # xblock-poll -xmlsec==1.3.13 +xmlsec==1.3.14 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # python3-saml xss-utils==0.6.0 diff --git a/requirements/edx/kernel.in b/requirements/edx/kernel.in index a5b510742ac7..7323c243accf 100644 --- a/requirements/edx/kernel.in +++ b/requirements/edx/kernel.in @@ -103,7 +103,7 @@ importlib_metadata # Used to access entry_points in i18n_api pl jsonfield # Django model field for validated JSON; used in several apps laboratory # Library for testing that code refactors/infrastructure changes produce identical results importlib_metadata # Used to access entry_points in i18n_api plugin -lxml # XML parser +lxml[html_clean] # XML parser lti-consumer-xblock>=7.3.0 mako # Primary template language used for server-side page rendering Markdown # Convert text markup to HTML; used in capa problems, forums, and course wikis diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 0c7d65b95c85..6984a56265da 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -57,6 +57,7 @@ asn1crypto==1.5.1 # snowflake-connector-python astroid==2.13.5 # via + # -c requirements/edx/../constraints.txt # pylint # pylint-celery attrs==24.2.0 @@ -531,7 +532,7 @@ edx-celeryutils==1.3.0 # -r requirements/edx/base.txt # edx-name-affirmation # super-csv -edx-codejail==3.5.1 +edx-codejail==3.5.2 # via -r requirements/edx/base.txt edx-completion==4.7.3 # via -r requirements/edx/base.txt @@ -571,7 +572,7 @@ edx-drf-extensions==10.5.0 # edx-when # edxval # openedx-learning -edx-enterprise==4.30.1 +edx-enterprise==4.33.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -607,7 +608,7 @@ edx-opaque-keys[django]==2.11.0 # ora2 edx-organizations==6.13.0 # via -r requirements/edx/base.txt -edx-proctoring==4.18.2 +edx-proctoring==4.18.3 # via # -r requirements/edx/base.txt # edx-proctoring-proctortrack @@ -620,7 +621,7 @@ edx-rest-api-client==6.0.0 # -r requirements/edx/base.txt # edx-enterprise # edx-proctoring -edx-search==4.1.0 +edx-search==4.1.1 # via -r requirements/edx/base.txt edx-sga==0.25.0 # via -r requirements/edx/base.txt @@ -899,13 +900,13 @@ loremipsum==1.0.5 # ora2 lti-consumer-xblock==9.11.3 # via -r requirements/edx/base.txt -lxml==4.9.4 +lxml[html-clean]==5.3.0 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-i18n-tools # edxval # lti-consumer-xblock + # lxml-html-clean # olxcleaner # openedx-calc # ora2 @@ -913,6 +914,10 @@ lxml==4.9.4 # python3-saml # xblock # xmlsec +lxml-html-clean==0.3.1 + # via + # -r requirements/edx/base.txt + # lxml mailsnake==1.6.4 # via -r requirements/edx/base.txt mako==1.3.6 @@ -1037,7 +1042,7 @@ openedx-filters==1.11.0 # -r requirements/edx/base.txt # lti-consumer-xblock # ora2 -openedx-learning==0.16.1 +openedx-learning==0.18.0 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt @@ -1047,7 +1052,7 @@ optimizely-sdk==4.1.1 # via # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt -ora2==6.12.2 +ora2==6.14.0 # via -r requirements/edx/base.txt packaging==24.1 # via @@ -1678,9 +1683,8 @@ xblock-utils==4.0.0 # -r requirements/edx/base.txt # edx-sga # xblock-poll -xmlsec==1.3.13 +xmlsec==1.3.14 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # python3-saml xss-utils==0.6.0 diff --git a/scripts/compile_sass.py b/scripts/compile_sass.py index ec1efee24d2b..14b84d003af6 100755 --- a/scripts/compile_sass.py +++ b/scripts/compile_sass.py @@ -352,18 +352,6 @@ def compile_sass_dir( repo / "lms" / "static" / "sass", ], ) - compile_sass_dir( - "Compiling built-in XBlock Sass for default LMS", - repo / "xmodule" / "assets", - repo / "lms" / "static" / "css", - includes=[ - *common_includes, - repo / "lms" / "static" / "sass" / "partials", - repo / "cms" / "static" / "sass" / "partials", - repo / "lms" / "static" / "sass", - repo / "cms" / "static" / "sass", - ], - ) if not skip_cms: compile_sass_dir( "Compiling default CMS Sass", @@ -376,18 +364,6 @@ def compile_sass_dir( repo / "cms" / "static" / "sass", ], ) - compile_sass_dir( - "Compiling built-in XBlock Sass for default CMS", - repo / "xmodule" / "assets", - repo / "cms" / "static" / "css", - includes=[ - *common_includes, - repo / "lms" / "static" / "sass" / "partials", - repo / "cms" / "static" / "sass" / "partials", - repo / "lms" / "static" / "sass", - repo / "cms" / "static" / "sass", - ], - ) click.secho(f"Done compiling default Sass!", fg="cyan", bold=True) click.echo() @@ -429,20 +405,6 @@ def compile_sass_dir( ], tolerate_missing=True, ) - compile_sass_dir( - "Compiling built-in XBlock Sass for themed LMS", - repo / "xmodule" / "assets", - theme / "lms" / "static" / "css", - includes=[ - *common_includes, - theme / "lms" / "static" / "sass" / "partials", - theme / "cms" / "static" / "sass" / "partials", - repo / "lms" / "static" / "sass" / "partials", - repo / "cms" / "static" / "sass" / "partials", - repo / "lms" / "static" / "sass", - repo / "cms" / "static" / "sass", - ], - ) if not skip_cms: compile_sass_dir( "Compiling default CMS Sass with themed partials", @@ -470,20 +432,6 @@ def compile_sass_dir( ], tolerate_missing=True, ) - compile_sass_dir( - "Compiling built-in XBlock Sass for themed CMS", - repo / "xmodule" / "assets", - theme / "cms" / "static" / "css", - includes=[ - *common_includes, - theme / "lms" / "static" / "sass" / "partials", - theme / "cms" / "static" / "sass" / "partials", - repo / "lms" / "static" / "sass" / "partials", - repo / "cms" / "static" / "sass" / "partials", - repo / "lms" / "static" / "sass", - repo / "cms" / "static" / "sass", - ], - ) click.secho(f"Done compiling Sass for theme at {theme}!", fg="cyan", bold=True) click.echo() diff --git a/scripts/gha-shards-readme.md b/scripts/gha-shards-readme.md index 9996a1f3b241..d7a1bd9ef7ab 100644 --- a/scripts/gha-shards-readme.md +++ b/scripts/gha-shards-readme.md @@ -32,4 +32,4 @@ You'd have to update the `unit-test-shards.json` file manually to fix this. ``` pytest --collect-only --ds=cms.envs.test cms/ ``` -For more details on how this check collects and compares the unit tests count please take a look at [verify unit tests count](../.github/workflows/verify-gha-unit-tests-count.yml) +For more details on how this check collects and compares the unit tests count please take a look at [verify unit tests count](../.github/workflows/unit-tests.yml) diff --git a/scripts/structures_pruning/README.rst b/scripts/structures_pruning/README.rst index c16a837a93af..77bc6bfbf0c8 100644 --- a/scripts/structures_pruning/README.rst +++ b/scripts/structures_pruning/README.rst @@ -29,11 +29,11 @@ To download the scripts, you can perform a partial clone of the edx-platform rep Create Python Virtual Environment --------------------------------- -Create a Python virtual environment using Python 3.8: +Create a Python virtual environment using Python 3.11: .. code-block:: bash - python3.8 -m venv ../venv + python3.11 -m venv ../venv source ../venv/bin/activate Install Pip Packages diff --git a/scripts/user_retirement/README.rst b/scripts/user_retirement/README.rst index d5417f7f7e75..380a2489108b 100644 --- a/scripts/user_retirement/README.rst +++ b/scripts/user_retirement/README.rst @@ -28,11 +28,11 @@ To download the scripts, you can perform a partial clone of the edx-platform rep Create Python Virtual Environment --------------------------------- -Create a Python virtual environment using Python 3.8: +Create a Python virtual environment using Python 3.11: .. code-block:: bash - python3.8 -m venv ../venv + python3.11 -m venv ../venv source ../venv/bin/activate Install Pip Packages diff --git a/scripts/user_retirement/requirements/base.txt b/scripts/user_retirement/requirements/base.txt index 288d660b14be..704baaff2c79 100644 --- a/scripts/user_retirement/requirements/base.txt +++ b/scripts/user_retirement/requirements/base.txt @@ -77,10 +77,8 @@ jmespath==1.0.1 # via # boto3 # botocore -lxml==4.9.4 - # via - # -c scripts/user_retirement/requirements/../../../requirements/constraints.txt - # zeep +lxml==5.3.0 + # via zeep more-itertools==10.5.0 # via simple-salesforce newrelic==10.2.0 diff --git a/scripts/user_retirement/requirements/testing.txt b/scripts/user_retirement/requirements/testing.txt index da4ce1b0391a..4cb3de607db7 100644 --- a/scripts/user_retirement/requirements/testing.txt +++ b/scripts/user_retirement/requirements/testing.txt @@ -116,7 +116,7 @@ jmespath==1.0.1 # -r scripts/user_retirement/requirements/base.txt # boto3 # botocore -lxml==4.9.4 +lxml==5.3.0 # via # -r scripts/user_retirement/requirements/base.txt # zeep diff --git a/scripts/user_retirement/utils/helpers.py b/scripts/user_retirement/utils/helpers.py index 1bcbadb4b3c4..804852833436 100644 --- a/scripts/user_retirement/utils/helpers.py +++ b/scripts/user_retirement/utils/helpers.py @@ -43,12 +43,11 @@ def _fail(kind, code, message): """ _log(kind, message) - # Try to get a traceback, if there is one. On Python 3.4 this raises an AttributeError - # if there is no current exception, so we eat that here. - try: - _log(kind, traceback.format_exc()) - except AttributeError: - pass + # Log the traceback if an exception is currently being handled + exc_type, exc_value, exc_traceback = sys.exc_info() + if exc_type is not None: + traceback_str = traceback.format_exception(exc_type, exc_value, exc_traceback) + _log(kind, ''.join(traceback_str)) sys.exit(code) @@ -66,16 +65,12 @@ def _get_error_str_from_exception(exc): """ Return a string from an exception that may or may not have a .content (Slumber) """ - exc_msg = text_type(exc) + exc_msg = str(exc) if hasattr(exc, 'content'): - # Slumber inconveniently discards the decoded .text attribute from the Response object, - # and instead gives us the raw encoded .content attribute, so we need to decode it first. - # Python 2 needs the decode, Py3 does not have it. - try: - exc_msg += '\n' + str(exc.content).decode('utf-8') - except AttributeError: - exc_msg += '\n' + str(exc.content) + # Attempt to decode `exc.content` if it's in bytes, otherwise just convert to str + exc_content = exc.content.decode('utf-8') if isinstance(exc.content, bytes) else str(exc.content) + exc_msg += '\n' + exc_content return exc_msg diff --git a/xmodule/assets/CustomTagBlockEditor.scss b/xmodule/assets/CustomTagBlockEditor.scss deleted file mode 100644 index 3abd162c808d..000000000000 --- a/xmodule/assets/CustomTagBlockEditor.scss +++ /dev/null @@ -1,3 +0,0 @@ -.xmodule_edit.xmodule_CustomTagBlock { - @import "codemirror/codemirror.scss"; -} diff --git a/xmodule/assets/LTIBlockDisplay.scss b/xmodule/assets/LTIBlockDisplay.scss deleted file mode 100644 index ea92202df2ad..000000000000 --- a/xmodule/assets/LTIBlockDisplay.scss +++ /dev/null @@ -1,3 +0,0 @@ -.xmodule_display.xmodule_LTIBlock { - @import "lti/lti.scss"; -} diff --git a/xmodule/assets/ProblemBlockDisplay.scss b/xmodule/assets/ProblemBlockDisplay.scss deleted file mode 100644 index 5175529246b0..000000000000 --- a/xmodule/assets/ProblemBlockDisplay.scss +++ /dev/null @@ -1,3 +0,0 @@ -.xmodule_display.xmodule_ProblemBlock { - @import "capa/display.scss"; -} diff --git a/xmodule/assets/ProblemBlockEditor.scss b/xmodule/assets/ProblemBlockEditor.scss deleted file mode 100644 index 03d3cf1e2a98..000000000000 --- a/xmodule/assets/ProblemBlockEditor.scss +++ /dev/null @@ -1,4 +0,0 @@ -.xmodule_edit.xmodule_ProblemBlock { - @import "editor/edit.scss"; - @import "problem/edit.scss"; -} diff --git a/xmodule/assets/README.rst b/xmodule/assets/README.rst index a697915db835..feb72168f9de 100644 --- a/xmodule/assets/README.rst +++ b/xmodule/assets/README.rst @@ -27,25 +27,15 @@ However, we are proactively working towards a system where: Themable Sass (.scss) ********************* -XBlock CSS for ``student_view``, ``author_view``, and ``public_view`` is compiled from the various ``./BlockDisplay.scss`` modules, such as `AnnotatableBlockDisplay.scss`_. - -XBlock CSS for ``studio_view`` is compiled from the various ``./BlockEditor.scss`` modules, such as `AnnotatableBlockEditor.scss`_. - -Those Sass modules are mostly thin wrappers around the underscore-prefixed Sass -modules within block-type-subdirectories, such as `annotatable/_display.css`. In the -future, we may `simplify things`_ by collapsing the top-level Sass modules and -just directly compiling the block-type-subdirectories' Sass. - -The CSS is compiled into the static folders of both LMS and CMS, as well as into -the corresponding folders in any enabled themes, as part of the edx-platform build. -It is collected into the static root, and then linked to from XBlock fragments by the -``add_sass_to_fragment`` function in `builtin_assets.py`_. - -.. _AnnotatableBlockDisplay.scss: https://github.com/openedx/edx-platform/tree/master/xmodule/assets/AnnotatableBlockDisplay.scss -.. _AnnotatableBlockEditor.scss: https://github.com/openedx/edx-platform/tree/master/xmodule/assets/AnnotatableBlockEditor.scss -.. _annotatable/_display.scss: https://github.com/openedx/edx-platform/tree/master/xmodule/assets/annotatable/_display.scss -.. _simplify things: https://github.com/openedx/edx-platform/issues/32621 - +Formerly, built-in XBlock CSS for ``student_view``, ``author_view``, and +``public_view`` was compiled from the various +``./BlockDisplay.scss`` modules, and ``studio_view`` CSS was +compiled from the various ``./BlockEditor.scss`` modules. + +As of November 2024, all that built-in XBlock Sass was been permanently +compiled into CSS, stored at ``../static/css-builtin-blocks/``. +The theme-overridable Sass variables are injected into CSS variables via +``../../common/static/sass/_builtin-block-variables.scss``. JavaScript (.js) **************** diff --git a/xmodule/assets/SequenceBlockDisplay.scss b/xmodule/assets/SequenceBlockDisplay.scss deleted file mode 100644 index 0c5aa3c82c49..000000000000 --- a/xmodule/assets/SequenceBlockDisplay.scss +++ /dev/null @@ -1,3 +0,0 @@ -.xmodule_display.xmodule_SequenceBlock { - @import "sequence/display.scss"; -} diff --git a/xmodule/assets/capa/_display.scss b/xmodule/assets/capa/_display.scss deleted file mode 100644 index 15571b65dc30..000000000000 --- a/xmodule/assets/capa/_display.scss +++ /dev/null @@ -1,1747 +0,0 @@ -// capa - styling -// ==================== - -// Table of Contents -// * +Variables - Capa -// * +Extends - Capa -// * +Mixins - Status Icon - Capa -// * +Resets - Deprecate Please -// * +Problem - Base -// * +Problem - Choice Group -// * +Problem - Misc, Unclassified Mess -// * +Problem - Text Input, Numerical Input -// * +Problem - Option Input (Dropdown) -// * +Problem - CodeMirror -// * +Problem - Misc, Unclassified Mess Part 2 -// * +Problem - Rubric -// * +Problem - Annotation -// * +Problem - Choice Text Group -// * +Problem - Image Input Overrides -// * +Problem - Annotation Problem Overrides -@import 'vendor/bi-app/bi-app-ltr'; -@import 'bourbon/bourbon'; -@import 'lms/theme/variables'; -@import 'bootstrap/scss/variables'; -@import 'lms/theme/variables-v1'; - -// +Variables - Capa -// ==================== -$annotation-yellow: rgba(255, 255, 10, 0.3); -$color-copy-tip: rgb(100, 100, 100); - -// FontAwesome Icon code -// ==================== -$checkmark-icon: '\f00c'; // .fa-check -$cross-icon: '\f00d'; // .fa-close -$asterisk-icon: '\f069'; // .fa-asterisk - - -@import '../../../../../static/sass/edx-pattern-library-shims/base/variables'; - -// +Extends - Capa -// ==================== -// Duplicated from _mixins.scss due to xmodule compilation, inheritance issues -%use-font-awesome { - font-family: FontAwesome; - -webkit-font-smoothing: antialiased; - display: inline-block; - speak: none; -} - -// +Mixins - Status Icon - Capa -// ==================== -@mixin status-icon($color: $gray, $fontAwesomeIcon: "\f00d") { - .status-icon { - &::after { - @extend %use-font-awesome; - - color: $color; - font-size: 1.2em; - content: $fontAwesomeIcon; - } - } -} - -// +Resets - Deprecate Please -// ==================== -h2 { - margin-top: 0; - margin-bottom: ($baseline*0.75); - - &.problem-header { - display: inline-block; - - section.staff { - margin-top: ($baseline*1.5); - font-size: 80%; - } - } - - @media print { - display: block; - width: auto; - border-right: 0; - } -} - -.explanation-title { - font-weight: bold; -} - -%feedback-hint { - margin-top: ($baseline / 4); - - .icon { - @include margin-right($baseline / 4); - } -} - -.feedback-hint-incorrect { - @extend %feedback-hint; - - .icon { - color: $incorrect; - } -} - -.feedback-hint-partially-correct, -.feedback-hint-correct { - @extend %feedback-hint; - - .icon { - color: $correct; - } -} - -.feedback-hint-text { - color: $color-copy-tip; -} - -.problem-hint { - margin-bottom: 20px; - width: 100%; -} - -.hint-label { - display: inline-block; - padding-right: 0.5em; -} - -.hint-text { - display: inline-block; -} - -.feedback-hint-multi .hint-text { - display: block; -} - -iframe[seamless] { - overflow: hidden; - padding: 0; - border: 0 none transparent; - background-color: transparent; -} - -.inline-error { - color: darken($error-color, 11%); -} - -div.problem-progress { - display: inline-block; - color: $gray-d1; - font-size: em(14); -} - -// +Problem - Base -// ==================== -div.problem { - padding-top: $baseline; - - @media print { - display: block; - padding: 0; - width: auto; - - canvas, - img { - page-break-inside: avoid; - } - } - - input.math { - direction: ltr; // Equations are always English - } - - .inline { - display: inline; - - + p { - margin-top: $baseline; - } - } - - .question-description { - color: $gray-d1; - font-size: $small-font-size; - } - - form > label, .problem-group-label { - display: block; - margin-bottom: $baseline; - font: inherit; - color: inherit; - -webkit-font-smoothing: initial; - } - - .problem-group-label + .question-description { - margin-top: -$baseline; - } - -} - -// CAPA gap spacing between problem parts -// can not use the & + & since .problem is nested deeply in .xmodule_display.xmodule_CapaModule -.wrapper-problem-response + .wrapper-problem-response, -.wrapper-problem-response + p { - margin-top: ($baseline * 1.5); -} - -// Choice Group - silent class -%choicegroup-base { - @include clearfix(); - - min-width: 100px; - width: auto !important; - width: 100px; - - label { - box-sizing: border-box; - - display: inline-block; - clear: both; - margin-bottom: ($baseline/2); - border: 2px solid $gray-l4; - border-radius: 3px; - padding: ($baseline/2); - width: 100%; - - &::after { - @include margin-left($baseline*0.75); - } - } - - .indicator-container { - min-height: 1px; - width: 25px; - display: inline-block; - } - - fieldset { - box-sizing: border-box; - } - - input[type="radio"], - input[type="checkbox"] { - @include margin($baseline/4); - @include margin-right($baseline/2); - } - - input { - &:focus, - &:hover { - & + label { - border: 2px solid $blue; - } - } - - &, - &:focus, - &:hover { - & + label.choicegroup_correct { - @include status-icon($correct, $checkmark-icon); - - border: 2px solid $correct; - } - - & + label.choicegroup_partially-correct { - @include status-icon($partially-correct, $asterisk-icon); - - border: 2px solid $partially-correct; - } - - & + label.choicegroup_incorrect { - @include status-icon($incorrect, $cross-icon); - - border: 2px solid $incorrect; - } - - & + label.choicegroup_submitted { - border: 2px solid $submitted; - } - } - } -} - -// +Problem - Choice Group -// ==================== -div.problem { - .choicegroup { - @extend %choicegroup-base; - - .field { - position: relative; - } - - label { - @include padding($baseline/2); - @include padding-left($baseline*2.3); - - position: relative; - font-size: $base-font-size; - line-height: normal; - cursor: pointer; - } - - input[type="radio"], - input[type="checkbox"] { - @include left(em(9)); - - position: absolute; - top: 0.35em; - width: $baseline*1.1; - height: $baseline*1.1; - z-index: 1; - } - - legend { - margin-bottom: $baseline; - max-width: 100%; - white-space: normal; - } - - legend + .question-description { - margin-top: -$baseline; - max-width: 100%; - white-space: normal; - } - } -} - -// +Problem - Status Indicators -// ==================== -// Summary status indicators shown after the input area -div.problem { - .indicator-container { - @include margin-left($baseline*0.75); - - .status { - width: $baseline; - - // CASE: correct answer - &.correct { - @include status-icon($correct, $checkmark-icon); - } - - // CASE: partially correct answer - &.partially-correct { - @include status-icon($partially-correct, $asterisk-icon); - } - - // CASE: incorrect answer - &.incorrect { - @include status-icon($incorrect, $cross-icon); - } - - &.submitted, - &.unsubmitted, - &.unanswered { - .status-icon { - content: ''; - } - } - } - } -} - -// +Problem - Misc, Unclassified Mess -// ==================== -div.problem { - ol.enumerate { - li { - &::before { - display: block; - visibility: hidden; - height: 0; - content: " "; - } - } - } - - .solution-span { - > span { - margin: $baseline 0; - display: block; - position: relative; - - &:empty { - display: none; - } - } - } - - .targeted-feedback-span { - > span { - display: block; - position: relative; - - &:empty { - display: none; - } - } - } - - // known classes using this div: .indicator-container, moved to section above - div { - - // TO-DO: Styling used by advanced capa problem types. Should be synced up to use .status class - p { - &.answer { - margin-top: -2px; - } - - span.clarification i { - font-style: normal; - - &:hover { - color: $blue; - } - } - } - - &.correct, &.ui-icon-check { - input { - border-color: $correct; - } - } - - &.partially-correct, &.ui-icon-check { - input { - border-color: $partially-correct; - } - } - - &.processing { - input { - border-color: #aaa; - } - } - - &.ui-icon-close { - input { - border-color: $incorrect; - } - } - - &.incorrect, &.incomplete { - input { - border-color: $incorrect; - } - } - - &.submitted, &.ui-icon-check { - input { - border-color: $submitted; - } - } - - p.answer { - display: inline-block; - margin-top: ($baseline / 2); - margin-bottom: 0; - - &::before { - @extend %t-strong; - - display: inline; - content: "Answer: "; - - } - - &:empty { - &::before { - display: none; - } - } - } - - div.equation { - clear: both; - margin-top: 3px; - - .MathJax_Display { - width: auto; - } - - img.loading { - @include padding-left($baseline/2); - - display: inline-block; - } - - span { - margin-bottom: 0; - display: inline-block; - - &.MathJax_CHTML, &.MathJax, &.MathJax_SVG { - padding: 6px; - min-width: 30px; - border: 1px solid #e3e3e3; - border-radius: 4px; - background: #f1f1f1; - } - } - } - - // Hides equation previews in symbolic response problems when printing - [id^='display'].equation { - @media print { - display: none; - } - } - - //TO-DO: review and deprecate all these styles within span {} - span { - &.ui-icon-bullet { - display: inline-block; - position: relative; - top: 4px; - width: 14px; - height: 14px; - background: url('#{$static-path}/images/unanswered-icon.png') center center no-repeat; - } - - &.processing, &.ui-icon-processing { - display: inline-block; - position: relative; - top: 6px; - width: 25px; - height: 20px; - background: url('#{$static-path}/images/spinner.gif') center center no-repeat; - } - - &.ui-icon-check { - display: inline-block; - position: relative; - top: 3px; - width: 25px; - height: 20px; - background: url('#{$static-path}/images/correct-icon.png') center center no-repeat; - } - - &.incomplete, &.ui-icon-close { - display: inline-block; - position: relative; - top: 3px; - width: 20px; - height: 20px; - background: url('#{$static-path}/images/incorrect-icon.png') center center no-repeat; - } - } - - .reload { - @include float(right); - - margin: ($baseline/2); - } - - - .grader-status { - @include clearfix(); - - margin: $baseline/2 0; - padding: $baseline/2; - border-radius: 5px; - background: $gray-l6; - - span { - display: block; - float: left; - overflow: hidden; - margin: -7px 7px 0 0; - text-indent: -9999px; - } - - .grading { - margin: 0px 7px 0 0; - padding-left: 25px; - background: url('#{$static-path}/images/info-icon.png') left center no-repeat; - text-indent: 0px; - } - - p { - float: left; - margin-bottom: 0; - text-transform: capitalize; - line-height: 20px; - } - - &.file { - margin-top: $baseline; - padding: $baseline 0 0 0; - border: 0; - border-top: 1px solid #eee; - background: $white; - - p.debug { - display: none; - } - - input { - float: left; - } - } - - } - - .evaluation { - p { - margin-bottom: ($baseline/5); - } - } - - - .feedback-on-feedback { - margin-right: $baseline; - height: 100px; - } - - .evaluation-response { - header { - text-align: right; - - a { - font-size: .85em; - } - } - } - - .evaluation-scoring { - .scoring-list { - margin-left: 3px; - list-style-type: none; - - li { - display:inline; - margin-left: 50px; - - &:first-child { - margin-left: 0; - } - - label { - font-size: .9em; - } - } - } - } - - .submit-message-container { - margin: $baseline 0px ; - } - } - - div.inline { - > span { - display: inline; - } - } - - ul { - margin-bottom: lh(); - margin-left: .75em; - margin-left: .75rem; - list-style: disc outside none; - } - - ol { - margin-bottom: lh(); - margin-left: .75em; - margin-left: .75rem; - list-style: decimal outside none; - } - - dl { - line-height: 1.4em; - } - - dl dt { - @extend %t-strong; - - } - - dl dd { - margin-bottom: 0; - } - - dd { - margin-left: .5em; - margin-left: .5rem; - } - - li { - margin-bottom: lh(0.5); - line-height: 1.4em; - - &:last-child { - margin-bottom: 0; - } - } - - p { - margin-bottom: lh(); - } - - table { - margin: lh() 0; - border-collapse: collapse; - table-layout: auto; - - td, th { - &.cont-justified-left { - text-align: left !important; // nasty, but needed to override the bad specificity of the xmodule css selectors - } - - &.cont-justified-right { - text-align: right !important; // nasty, but needed to override the bad specificity of the xmodule css selectors - } - - &.cont-justified-center { - text-align: center !important; // nasty, but needed to override the bad specificity of the xmodule css selectorsstyles - } - } - - th { - @extend %t-strong; - - text-align: left; - } - - td { - text-align: left; - } - - caption, th, td { - padding: .25em .75em .25em 0; - padding: .25rem .75rem .25rem 0; - } - - caption { - margin-bottom: .75em; - margin-bottom: .75rem; - padding: .75em 0; - padding: .75rem 0; - background: #f1f1f1; - } - - tr, td, th { - vertical-align: middle; - } - } - - code { - margin: 0 2px; - padding: 0px 5px; - border: 1px solid #eaeaea; - border-radius: 3px; - background-color: $gray-l6; - white-space: nowrap; - font-size: .9em; - } - - pre { - overflow: auto; - padding: 6px $baseline/2; - border: 1px solid $gray-l3; - border-radius: 3px; - background-color: $gray-l6; - font-size: .9em; - line-height: 1.4; - - > code { - margin: 0; - padding: 0; - border: none; - background: transparent; - white-space: pre; - } - } -} - -// +Problem - Text Input, Numerical Input -// ==================== -.problem { - .capa_inputtype.textline, .inputtype.formulaequationinput { - input { - box-sizing: border-box; - - border: 2px solid $gray-l4; - border-radius: 3px; - min-width: 160px; - height: 46px; - } - - .status { - display: inline-block; - margin-top: ($baseline/2); - background: none; - } - - // CASE: incorrect answer - > .incorrect { - input { - border: 2px solid $incorrect; - } - - .status { - @include status-icon($incorrect, $cross-icon); - } - } - - // CASE: partially correct answer - > .partially-correct { - input { - border: 2px solid $partially-correct; - } - - .status { - @include status-icon($partially-correct, $asterisk-icon); - } - } - - // CASE: correct answer - > .correct { - input { - border: 2px solid $correct; - } - - .status { - @include status-icon($correct, $checkmark-icon); - } - } - - // CASE: submitted, correctness withheld - > .submitted { - input { - border: 2px solid $submitted; - } - - .status { - content: ''; - } - } - - // CASE: unanswered and unsubmitted - > .unanswered, > .unsubmitted { - input { - border: 2px solid $gray-l4; - } - - .status { - .status-icon { - &::after { - content: ''; - display: inline-block; - } - } - } - } - - } - - .inputtype.formulaequationinput { - > div { - input { - direction: ltr; - @include text-align(left); - } - } - } - - .trailing_text { - @include margin-right($baseline/2); - - display: inline-block; - } -} - - -// +Problem - Option Input (Dropdown) -// ==================== -.problem { - .inputtype.option-input { - margin: 0 0 0 0 !important; - - .indicator-container { - display: inline-block; - - .status.correct::after, - .status.partially-correct::after, - .status.incorrect::after, - .status.submitted::after, - .status.unanswered::after { - @include margin-left(0); - } - } - } -} - -// +Problem - CodeMirror -// ==================== -div.problem { - .CodeMirror { - border: 1px solid black; - font-size: 14px; - line-height: 18px; - resize: none; - - .cm-tab { - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAGFJREFUSMft1LsRQFAQheHPowAKoACx3IgEKtaEHujDjORSgWTH/ZOdnZOcM/sgk/kFFWY0qV8foQwS4MKBCS3qR6ixBJvElOobYAtivseIE120FaowJPN75GMu8j/LfMwNjh4HUpwg4LUAAAAASUVORK5CYII=); - background-position: right; - background-repeat: no-repeat; - } - - pre { - overflow: hidden; - margin: 0; - padding: 0; - border-width: 0; - border-radius: 0; - background: transparent; - white-space: pre; - word-wrap: normal; - font-size: inherit; - font-family: inherit; - resize: none; - - &.CodeMirror-cursor { - @extend %ui-depth1; - - position: absolute; - visibility: hidden; - width: 0; - border-right: none; - border-left: 1px solid $black; - } - } - } - - .CodeMirror-focused pre.CodeMirror-cursor { - visibility: visible; - } - - .CodeMirror-code pre { - width: -webkit-fit-content; - width: -moz-fit-content; - width: fit-content; - } - - .CodeMirror-scroll { - margin-right: 0px; - } -} - -.capa-message { - display: inline-block; - color: $gray-d1; - -webkit-font-smoothing: antialiased; -} - -// +Problem - Actions -// ==================== -div.problem .action { - min-height: $baseline; - width: 100%; - display: flex; - display: -ms-flexbox; - -ms-flex-align: start; - flex-direction: row; - align-items: center; - flex-wrap: wrap; - - .problem-action-buttons-wrapper { - display: inline-flex; - justify-content: flex-end; - width: 100%; - padding-bottom: $baseline; - } - - .problem-action-button-wrapper { - @include border-right(1px solid $gray-300); - @include padding(0, 13px); // to create a 26px gap, which is an a11y recommendation - - display: inline-block; - - &:last-child { - border: none; - padding-right: 0; - } - } - - .problem-action-btn { - border: none; - max-width: 110px; - - &:hover, - &:focus, - &:active { - color: $primary !important; - } - - .icon { - margin-bottom: $baseline / 10; - display: block; - } - - @media print { - display: none; - } - } - - .submit-attempt-container { - padding-bottom: $baseline; - flex-grow: 1; - display: flex; - align-items: center; - - @media (max-width: $bp-screen-lg) { - max-width: 100%; - padding-bottom: $baseline; - } - - .submit { - @include margin-right($baseline / 2); - @include float(left); - - white-space: nowrap; - } - - .submit-cta-description { - color: $primary; - font-size: small; - padding-right: $baseline / 2; - } - .submit-cta-link-button { - color: $primary; - padding-right: $baseline / 4; - } - } - - .submission-feedback { - @include margin-right($baseline / 2); - - margin-top: $baseline / 2; - display: inline-block; - color: $gray-d1; - font-size: $medium-font-size; - -webkit-font-smoothing: antialiased; - vertical-align: middle; - - &.cta-enabled { - margin-top: 0; - } - } -} - - -// +Problem - Misc, Unclassified Mess Part 2 -// ==================== -div.problem { - hr { - float: none; - clear: both; - margin: 0 0 .75rem; - width: 100%; - height: 1px; - border: none; - background: #ddd; - color: #ddd; - } - - .hidden { - display: none; - visibility: hidden; - } - - #{$all-text-inputs} { - display: inline; - width: auto; - } - - // this supports a deprecated element and should be removed once the center tag is removed - center { - display: block; - margin: lh() 0; - padding: lh(); - border: 1px solid $gray-l3; - } - - .message { - font-size: inherit; - } - - .detailed-solution > p { - margin: 0; - - &:first-child { - @extend %t-strong; - - margin-bottom: 0; - } - - } - - .detailed-targeted-feedback, - .detailed-targeted-feedback-partially-correct, - .detailed-targeted-feedback-correct { - > p { - margin: 0; - font-weight: normal; - - &:first-child { - @extend %t-strong; - } - } - } - - div.capa_alert { - margin-top: $baseline; - padding: 8px 12px; - border: 1px solid $warning-color; - border-radius: 3px; - background: $warning-color-accent; - font-size: 0.9em; - } - - .notification { - @include float(left); - - margin-top: $baseline / 2; - padding: ($baseline / 2.5) ($baseline / 2) ($baseline / 5) ($baseline / 2); - line-height: $base-line-height; - - &.success { - @include notification-by-type($success); - } - - &.error { - @include notification-by-type($danger); - } - - &.warning { - @include notification-by-type($warning); - } - - &.general { - @include notification-by-type($general-color-accent); - } - - &.problem-hint { - border: 1px solid $uxpl-gray-background; - border-radius: 6px; - - .icon { - @include margin-right(3 * $baseline / 4); - - color: $uxpl-gray-dark; - } - - li { - color: $uxpl-gray-base; - - strong { - color: $uxpl-gray-dark; - } - } - } - - .icon { - @include float(left); - - position: relative; - top: $baseline / 5; - } - - .notification-message { - display: inline-block; - width: flex-grid(7,10); - // Make notification tall enough that when the "Review" button is displayed, - // the notification does not grow in height. - margin-bottom: 8px; - - ol { - list-style: none outside none; - padding: 0; - margin: 0; - - li:not(:last-child) { - margin-bottom: $baseline / 4; - } - } - } - - .notification-btn-wrapper { - @include float(right); - } - - } - - .notification-btn { - @include float(right); - - padding: ($baseline / 10) ($baseline / 4); - min-width: ($baseline * 3); - display: block; - clear: both; - - &:first-child { - margin-bottom: $baseline / 4; - } - } - - // override default button hover - button { - &:hover { - background-image: none; - box-shadow: none; - } - - &:focus { - box-shadow: none; - } - - &.btn-default { - background-color: transparent; - } - - &.btn-brand { - &:hover { - background-color: $btn-brand-focus-background; - } - } - } - - .review-btn { - color: $blue; // notification type has other colors - &.sr { - color: $blue; - } - } - - div.capa_reset { - padding: 25px; - border: 1px solid $error-color; - background-color: lighten($error-color, 25%); - border-radius: 3px; - font-size: 1em; - margin-top: $baseline/2; - margin-bottom: $baseline/2; - } - - .capa_reset>h2 { - color: #a00; - } - - .capa_reset li { - font-size: 0.9em; - } - - .hints { - border: 1px solid $gray-l3; - - h3 { - @extend %t-strong; - - padding: 9px; - border-bottom: 1px solid #e3e3e3; - background: #eee; - text-shadow: 0 1px 0 $white; - font-size: em(16); - } - - div { - border-bottom: 1px solid #ddd; - - &:last-child { - border-bottom: none; - } - - p { - margin-bottom: 0; - } - - header { - a { - display: block; - padding: 9px; - background: $gray-l6; - box-shadow: inset 0 0 0 1px $white; - } - } - - > section { - padding: 9px; - } - } - } - - .test { - padding-top: 18px; - - header { - margin-bottom: 12px; - - h3 { - @extend %t-strong; - - color: #aaa; - font-style: normal; - font-size: 0.9em; - } - } - - > section { - position: relative; - margin-bottom: ($baseline/2); - padding: 9px 9px $baseline; - border: 1px solid #ddd; - border-radius: 3px; - background: $white; - box-shadow: inset 0 0 0 1px #eee; - - p:last-of-type { - margin-bottom: 0; - } - - .shortform { - margin-bottom: .6em; - } - - a.full { - @include position(absolute, 0 0 1px 0); - box-sizing: border-box; - - display: block; - padding: ($baseline/5); - background: $gray-l4; - text-align: right; - font-size: 1em; - - &.full-top { - @include position(absolute, 1px 0 auto 0); - } - - &.full-bottom { - @include position(absolute, auto 0 1px 0); - } - } - } - } - - .external-grader-message { - section { - padding-top: ($baseline*1.5); - padding-left: $baseline; - background-color: #fafafa; - color: #2c2c2c; - font-size: 1em; - font-family: monospace; - - header { - font-size: 1.4em; - } - - .shortform { - @extend %t-strong; - } - - .longform { - margin: 0; - padding: 0; - - .result-errors { - margin: ($baseline/4); - padding: ($baseline/2) ($baseline/2) ($baseline/2) ($baseline*2); - background: url('#{$static-path}/images/incorrect-icon.png') center left no-repeat; - - li { - color: #b00; - } - } - - .result-output { - margin: $baseline/4; - padding: $baseline 0 ($baseline*0.75) 50px; - border-top: 1px solid #ddd; - border-left: $baseline solid #fafafa; - - h4 { - font-size: 1em; - font-family: monospace; - } - - dl { - margin: 0; - } - - dt { - margin-top: $baseline; - } - - dd { - margin-left: 24pt; - } - } - - .result-correct { - background: url('#{$static-path}/images/correct-icon.png') left 20px no-repeat; - - .result-actual-output { - color: #090; - } - } - - .result-partially-correct { - background: url('#{$static-path}/images/partially-correct-icon.png') left 20px no-repeat; - - .result-actual-output { - color: #090; - } - } - - .result-incorrect { - background: url('#{$static-path}/images/incorrect-icon.png') left 20px no-repeat; - - .result-actual-output { - color: #b00; - } - } - - .markup-text{ - margin: ($baseline/4); - padding: $baseline 0 15px 50px; - border-top: 1px solid #ddd; - border-left: 20px solid #fafafa; - - bs { - color: #b00; - } - - bg { - color: #bda046; - } - } - } - } - } -} - - -// +Problem - Rubric -// ==================== -div.problem { - .rubric { - tr { - margin: ($baseline/2) 0; - height: 100%; - } - - td { - margin: ($baseline/2) 0; - padding: $baseline 0; - height: 100%; - } - - th { - margin: ($baseline/4); - padding: ($baseline/4); - } - - label, - .view-only { - position: relative; - display: inline-block; - margin: 3px; - padding: ($baseline*0.75); - min-width: 50px; - min-height: 50px; - width: 150px; - height: 100%; - background-color: $gray-l3; - font-size: .9em; - } - - .grade { - position: absolute; - right: 0; - bottom: 0; - margin: ($baseline/2); - } - - .selected-grade { - background: #666; - color: white; - } - - input[type=radio]:checked + label { - background: #666; - color: white; - } - - input[class='score-selection'] { - display: none; - } - } -} - -// +Problem - Annotation -// ==================== -div.problem { - .annotation-input { - margin: 0 0 1em 0; - border: 1px solid $gray-l3; - border-radius: 1em; - - .annotation-header { - @extend %t-strong; - - padding: .5em 1em; - border-bottom: 1px solid $gray-l3; - } - - .annotation-body { padding: .5em 1em; } - - a.annotation-return { - float: right; - font: inherit; - font-weight: normal; - } - - a.annotation-return::after { content: " \2191" } - - .block, ul.tags { - margin: .5em 0; - padding: 0; - } - - .block-highlight { - padding: .5em; - border: 1px solid darken($annotation-yellow, 10%); - background-color: $annotation-yellow; - color: #333; - font-style: normal; - } - - .block-comment { font-style: italic; } - - ul.tags { - display: block; - margin-left: 1em; - list-style-type: none; - - li { - position: relative; - display: block; - margin: 1em 0 0 0; - - .tag { - @extend %ui-fake-link; - - display: inline-block; - margin-left: ($baseline*2); - border: 1px solid rgb(102,102,102); - - &.selected { - background-color: $annotation-yellow; - } - } - - .tag-status { - position: absolute; - left: 0; - } - .tag-status, .tag { padding: .25em .5em; } - } - } - - textarea.comment { - $num-lines-to-show: 5; - $line-height: 1.4em; - $padding: .2em; - - padding: $padding (2 * $padding); - width: 100%; - height: ($num-lines-to-show * $line-height) + (2*$padding) - (($line-height - 1)/2); - line-height: $line-height; - } - - .answer-annotation { display: block; margin: 0; } - - /* for debugging the input value field. enable the debug flag on the inputtype */ - .debug-value { - margin: 1em 0; - padding: 1em; - border: 1px solid $black; - background-color: #999; - color: $white; - - input[type="text"] { width: 100%; } - - pre { background-color: $gray-l3; color: $black; } - - &::before { - @extend %t-strong; - - display: block; - content: "debug input value"; - font-size: 1.5em; - } - } - } -} - -// +Problem - Choice Text Group -// ==================== -div.problem { - .choicetextgroup { - @extend %choicegroup-base; - - input[type="text"]{ - margin-bottom: 0.5em; - } - - label.choicetextgroup_correct, section.choicetextgroup_correct { - @extend label.choicegroup_correct; - - input[type="text"] { - border-color: $correct; - } - } - - label.choicetextgroup_partially-correct, section.choicetextgroup_partially-correct { - @extend label.choicegroup_partially-correct; - - input[type="text"] { - border-color: $partially-correct; - } - } - - label.choicetextgroup_incorrect, section.choicetextgroup_incorrect { - @extend label.choicegroup_incorrect; - } - - label.choicetextgroup_submitted, section.choicetextgroup_submitted { - @extend label.choicegroup_submitted; - } - - label.choicetextgroup_show_correct, section.choicetextgroup_show_correct { - &::after { - @include margin-left($baseline*0.75); - - content: url('#{$static-path}/images/correct-icon.png'); - } - } - - span.mock_label { - cursor : default; - } - } -} - -// +Problem - Image Input Overrides -// ==================== - -// NOTE: temporary override until image inputs use same base html structure as other common capa input types. -div.problem .imageinput.capa_inputtype { - .status { - display: inline-block; - position: relative; - top: 3px; - width: 25px; - height: 20px; - - &.unsubmitted, - &.unanswered { - .status-icon { - content: ''; - } - - .status-message { - display: none; - } - } - } - - .correct { - @include status-icon($correct, $checkmark-icon); - } - - .incorrect { - @include status-icon($incorrect, $cross-icon); - } - - .partially-correct { - @include status-icon($partially-correct, $asterisk-icon); - } - - .submitted { - content: ''; - } -} - -// +Problem - Annotation Problem Overrides -// ==================== - -// NOTE: temporary override until annotation problem inputs use same base html structure as other common capa input types. -div.problem .annotation-input { - .tag-status { - display: inline-block; - position: relative; - top: 3px; - width: 25px; - height: 20px; - - &.unsubmitted, - &.unanswered { - .status-icon { - content: ''; - } - - .status-message { - display: none; - } - } - } - - .correct { - @include status-icon($correct, $checkmark-icon); - } - - .incorrect { - @include status-icon($incorrect, $cross-icon); - } - - .partially-correct { - @include status-icon($partially-correct, $asterisk-icon); - } - - .submitted { - content: ''; - } -} - -// Loading Spinner -// ==================== -.problems-wrapper .loading-spinner { - text-align: center; - color: $gray-d1; -} diff --git a/xmodule/assets/codemirror/_codemirror.scss b/xmodule/assets/codemirror/_codemirror.scss deleted file mode 100644 index bf60ea6a49aa..000000000000 --- a/xmodule/assets/codemirror/_codemirror.scss +++ /dev/null @@ -1,5 +0,0 @@ -.CodeMirror { - background: #fff; - font-size: 13px; - color: #3c3c3c; -} diff --git a/xmodule/assets/editor/_edit.scss b/xmodule/assets/editor/_edit.scss deleted file mode 100644 index 0af846f8b201..000000000000 --- a/xmodule/assets/editor/_edit.scss +++ /dev/null @@ -1,83 +0,0 @@ -@import 'vendor/bi-app/bi-app-ltr'; -@import 'bourbon/bourbon'; -@import 'lms/theme/variables'; -@import 'bootstrap/scss/variables'; -@import 'cms/theme/variables-v1'; -@import 'mixins'; - -// This is shared CSS between the xmodule problem editor and the xmodule HTML editor. -.editor { - position: relative; - - .row { - position: relative; - } - - .editor-bar { - @include clearfix(); - @include linear-gradient(top, #d4dee8, #c9d5e2); - - position: relative; - padding: calc(var(--baseline)/4); - border-bottom-color: #a5aaaf; - - button { - display: inline-block; - - @include float(left); - - padding: 3px calc(var(--baseline)/2) 5px; - margin-left: 7px; - border: 0; - border-radius: 2px; - background: transparent; - - .icon { - height: 21px; - } - - &:hover, - &:focus { - background: rgba(255, 255, 255, .5); - } - } - } - - .editor-tabs { - position: absolute; - top: 10px; - - @include right(10px); - @include text-align(left); - @include direction(); - - li { - @include float(left); - @include margin-right(calc(var(--baseline)/4)); - - &:last-child { - @include margin-right(0); - } - } - - .tab { - display: block; - height: 24px; - padding: 7px 20px 3px; - border: 1px solid #a5aaaf; - border-radius: 3px 3px 0 0; - - @include linear-gradient(top, var(--transparent) 87%, rgba(0, 0, 0, .06)); - - background-color: #e5ecf3; - font-size: 13px; - color: #3c3c3c; - box-shadow: 1px -1px 1px rgba(0, 0, 0, .05); - - &.current { - background: var(--white); - border-bottom-color: var(--white); - } - } - } -} diff --git a/xmodule/assets/lti/_lti.scss b/xmodule/assets/lti/_lti.scss deleted file mode 100644 index 4bd2c41317f5..000000000000 --- a/xmodule/assets/lti/_lti.scss +++ /dev/null @@ -1,65 +0,0 @@ -@import 'bourbon/bourbon'; -@import 'lms/theme/variables'; -@import 'bootstrap/scss/variables'; -@import 'lms/theme/variables-v1'; -@import 'base/mixins'; - -h2.problem-header { - display: inline-block; -} - -div.problem-progress { - display: inline-block; - padding-left: ($baseline/4); - color: #666; - font-weight: 100; - font-size: em(16); -} - - -div.lti { - // align center - margin: 0 auto; - - .wrapper-lti-link { - @include font-size(14); - - background-color: $sidebar-color; - padding: $baseline; - - .lti-link { - margin-bottom: 0; - text-align: right; - - .link_lti_new_window { - @extend .gray-button; - - @include font-size(13); - @include line-height(14); - } - } - } - - form.ltiLaunchForm { - display: none; - } - - iframe.ltiLaunchFrame { - width: 100%; - height: 800px; - display: block; - border: 0px; - } - - h4.problem-feedback-label { - font-weight: 100; - font-size: em(16); - font-family: "Source Sans", "Open Sans", Verdana, Geneva, sans-serif, sans-serif; - } - - div.problem-feedback { - margin-top: ($baseline/4); - margin-bottom: ($baseline/4); - - } -} diff --git a/xmodule/assets/problem/_edit.scss b/xmodule/assets/problem/_edit.scss deleted file mode 100644 index 018a0961c247..000000000000 --- a/xmodule/assets/problem/_edit.scss +++ /dev/null @@ -1,109 +0,0 @@ -.editor-bar { - .editor-tabs { - .advanced-toggle { - height: auto; - margin-top: -4px; - padding: 3px 9px; - font-size: 12px; - color: $link-color; - - &.current { - border: 1px solid $lightGrey !important; - border-radius: 3px !important; - background: $lightGrey !important; - color: $darkGrey !important; - pointer-events: none; - cursor: none; - - &:hover, - &:focus { - box-shadow: 0 0 0 0 !important; - background-color: $white; - } - } - } - - } -} - -.simple-editor-cheatsheet { - position: absolute; - top: 41px; - @include left(70%); - width: 0; - border-left: 1px solid $gray-l2; - - background-color: $lightGrey; - overflow: hidden; - - &.shown { - width: 30%; - height: 92%; - overflow-y: scroll; - } - - .cheatsheet-wrapper { - padding: 5%; - } - - h6 { - margin-top: 4px; - margin-bottom: 7px; - margin-left: 4px; - font-size: 15px; - font-weight: 700; - display: inline-block; - vertical-align: top; - } - - .row { - @include clearfix(); - - padding-bottom: 5px !important; - margin-bottom: 10px !important; - border-bottom: 1px solid #ddd !important; - - &:last-child { - border-bottom: none !important; - margin-bottom: 0 !important; - } - } - - .col { - display: block; - - &.sample { - margin-right: 30px; - - .icon { - height: ($baseline * 1.5); - } - } - } - - pre { - font-size: 12px; - line-height: 18px; - } - - code { - padding: 0; - background: none; - } -} - -.problem-editor { - // adding padding to simple editor only - adjacent selector is needed since there are no toggles for CodeMirror - .markdown-box + .CodeMirror { - padding: 10px; - width: 69%; - } -} - -.problem-editor-icon { - display: inline-block; - width: 26px; - height: 21px; - vertical-align: middle; - color: $body-color; -} diff --git a/xmodule/assets/sequence/_display.scss b/xmodule/assets/sequence/_display.scss deleted file mode 100644 index 3ddda8b37d09..000000000000 --- a/xmodule/assets/sequence/_display.scss +++ /dev/null @@ -1,340 +0,0 @@ -@import 'vendor/bi-app/bi-app-ltr'; -@import 'bourbon/bourbon'; -@import 'lms/theme/variables'; -@import 'bootstrap/scss/variables'; -@import 'bootstrap/scss/mixins/breakpoints'; -@import 'lms/theme/variables-v1'; - -$seq-nav-border-color: $border-color !default; -$seq-nav-hover-color: rgb(245, 245, 245) !default; -$seq-nav-link-color: $link-color !default; -$seq-nav-icon-color: rgb(10, 10, 10) !default; -$seq-nav-icon-color-muted: rgb(90, 90, 90) !default; -$seq-nav-tooltip-color: rgb(51, 51, 51) !default; -$seq-nav-height: 50px; - -// repeated extends - needed since LMS styling was referenced -.block-link { - @include border-left(1px solid $seq-nav-border-color); - - display: block; - - &:hover, - &:focus { - background: none; - } -} - -.topbar { - @include clearfix(); - - border-bottom: 1px solid $seq-nav-border-color; - - @media print { - display: none; - } - - a { - &.block-link { - @include border-left(1px solid $seq-nav-border-color); - - display: block; - - &:hover, - &:focus { - background: none; - } - } - } -} - -%ui-clear-button { - background-color: transparent; - background-image: none; - background-position: center 14px; - background-repeat: no-repeat; - border: none; - border-radius: 0; - background-clip: border-box; - box-shadow: none; - box-sizing: content-box; - font-family: inherit; - font-size: inherit; - font-weight: inherit; -} - - -// ==================== - -.sequence-nav { - @extend .topbar; - - margin: 0 auto $baseline; - position: relative; - border-bottom: none; - z-index: 0; - height: $seq-nav-height; - display: flex; - justify-content: center; - - @media print { - display: none; - } - - .sequence-list-wrapper { - @extend %ui-depth2; - - position: relative; - height: 100%; - flex-grow: 1; - - @include media-breakpoint-down(xs) { - white-space: nowrap; - overflow-x: scroll; - } - } - - ol { - display: flex; - - li { - box-sizing: border-box; - min-width: 40px; - flex-grow: 1; - border-color: $seq-nav-border-color; - border-width: 1px; - border-top-style: solid; - - &:not(:last-child) { - @include border-right-style(solid); - } - - button { - @extend %ui-fake-link; - @extend %ui-clear-button; - - width: 100%; - height: ($seq-nav-height - 1); - position: relative; - margin: 0; - padding: 0; - display: block; - text-align: center; - border-color: $seq-nav-border-color; - border-width: 1px; - border-bottom-style: solid; - box-sizing: border-box; - overflow: visible; // for tooltip - IE11 uses 'hidden' by default if width/height is specified - - .icon { - display: inline-block; - line-height: 100%; // This matches the height of the its within (the parent) to get vertical centering. - font-size: 110%; - color: $seq-nav-icon-color-muted; - } - - .fa-bookmark { - color: $seq-nav-link-color; - } - - //video - &.seq_video { - .icon::before { - content: "\f008"; // .fa-film - } - } - - //other - &.seq_other { - .icon::before { - content: "\f02d"; // .fa-book - } - } - - //vertical - &.seq_vertical { - .icon::before { - content: "\f00b"; // .fa-tasks - } - } - - //problems - &.seq_problem { - .icon::before { - content: "\f044"; // .fa-pencil-square-o - } - } - - .sequence-tooltip { - @include text-align(left); - - @extend %ui-depth2; - - margin-top: 12px; - background: $seq-nav-tooltip-color; - color: $white; - font-family: $font-family-sans-serif; - line-height: lh(); - right: 0; // Should not be RTLed, tooltips do not move in RTL - padding: 6px; - position: absolute; - top: 48px; - text-shadow: 0 -1px 0 $black; - white-space: pre; - pointer-events: none; - - &:empty { - background: none; - - &::after { - display: none; - } - } - - &::after { - @include transform(rotate(45deg)); - @include right(18px); - - background: $seq-nav-tooltip-color; - content: " "; - display: block; - height: 10px; - right: 18px; // Not RTLed, positions tooltips relative to seq nav item - position: absolute; - top: -5px; - width: 10px; - } - } - } - } - } - - body.touch-based-device & ol li button:hover p { - display: none; - } -} - -.sequence-nav-button { - @extend %ui-depth3; - - display: block; - top: 0; - min-width: 40px; - max-width: 40px; - height: 100%; - text-shadow: none; // overrides default button text-shadow - background: none; // overrides default button gradient - background-color: theme-color("inverse"); - border-color: $seq-nav-border-color; - box-shadow: none; - font-size: inherit; - font-weight: normal; - padding: 0; - white-space: nowrap; - overflow-x: hidden; - - @include media-breakpoint-up(md) { - min-width: 120px; - max-width: 200px; - text-overflow: ellipsis; - - span:not(:last-child) { - @include padding-right($baseline / 2); - } - } - - .sequence-nav-button-label { - display: none; - - @include media-breakpoint-up(md) { - display: inline; - } - } - - &.button-previous { - order: -999; - - @include media-breakpoint-up(md) { - @include left(0); - @include border-top-left-radius(3px); - @include border-top-right-radius(0); - @include border-bottom-right-radius(0); - @include border-bottom-left-radius(3px); - } - } - - &.button-next { - order: 999; - - @include media-breakpoint-up(md) { - @include right(0); - @include border-top-left-radius(0); - @include border-top-right-radius(3px); - @include border-bottom-right-radius(3px); - @include border-bottom-left-radius(0); - } - } - - &.disabled { - cursor: normal; - } -} - -.seq_contents { - display: none; -} - -.sequence-bottom { - position: relative; - height: 45px; - margin: lh(2) auto; - display: flex; - justify-content: center; - - .sequence-nav-button { - position: relative; - min-width: 120px; - max-width: 200px; - text-overflow: ellipsis; - - &:last-of-type { - @include border-left(none); - } - } - - @media print { - display: none; - } -} - -#seq_content { - &:focus, - &:active { - outline: none; - } -} - -// hover and active states -.sequence-nav-button, -.sequence-nav button { - &.focused, - &:hover, - &:active, - &.active { - padding-top: 2px; - background-color: theme-color("primary"); - - .icon { - color: theme-color("inverse"); - } - - @include media-breakpoint-up(sm) { - border-bottom: 3px solid $seq-nav-link-color; - background-color: theme-color("inverse"); - - .icon { - color: $seq-nav-icon-color; - } - } - } -} diff --git a/xmodule/assets/tabs/_codemirror.scss b/xmodule/assets/tabs/_codemirror.scss deleted file mode 100644 index 237d1850332a..000000000000 --- a/xmodule/assets/tabs/_codemirror.scss +++ /dev/null @@ -1,20 +0,0 @@ -.editor { - @include clearfix(); - - .CodeMirror { - box-sizing: border-box; - - width: 100%; - position: relative; - height: 379px; - border: 1px solid #3c3c3c; - border-top: 1px solid #8891a1; - background: $white; - color: #3c3c3c; - } - - .CodeMirror-scroll { - height: 100%; - } -} - diff --git a/xmodule/capa/safe_exec/tests/test_safe_exec.py b/xmodule/capa/safe_exec/tests/test_safe_exec.py index fa12e4f69903..36d2cc965710 100644 --- a/xmodule/capa/safe_exec/tests/test_safe_exec.py +++ b/xmodule/capa/safe_exec/tests/test_safe_exec.py @@ -306,13 +306,6 @@ def equal_but_different_dicts(self): """ d1 = {k: 1 for k in "abcdefghijklmnopqrstuvwxyz"} d2 = {k: 1 for k in "bcdefghijklmnopqrstuvwxyza"} - # TODO: remove the next lines once we are in python3.8 - # since python3.8 dict preserve the order of insertion - # and therefore d2 and d1 keys are already in different order. - for i in range(10000): - d2[i] = 1 - for i in range(10000): - del d2[i] # Check that our dicts are equal, but with different key order. assert d1 == d2 diff --git a/xmodule/capa_block.py b/xmodule/capa_block.py index c1c650144b05..24737b689845 100644 --- a/xmodule/capa_block.py +++ b/xmodule/capa_block.py @@ -37,7 +37,7 @@ from xmodule.graders import ShowCorrectness from xmodule.raw_block import RawMixin from xmodule.util.sandboxing import SandboxService -from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_sass_to_fragment +from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_css_to_fragment from xmodule.x_module import ( ResourceTemplates, XModuleMixin, @@ -361,7 +361,7 @@ def student_view(self, _context, show_detailed_errors=False): else: html = self.get_html() fragment = Fragment(html) - add_sass_to_fragment(fragment, "ProblemBlockDisplay.scss") + add_css_to_fragment(fragment, "ProblemBlockDisplay.css") add_webpack_js_to_fragment(fragment, 'ProblemBlockDisplay') shim_xmodule_js(fragment, 'Problem') return fragment @@ -393,7 +393,7 @@ def studio_view(self, _context): fragment = Fragment( self.runtime.service(self, 'mako').render_cms_template(self.mako_template, self.get_context()) ) - add_sass_to_fragment(fragment, 'ProblemBlockEditor.scss') + add_css_to_fragment(fragment, 'ProblemBlockEditor.css') add_webpack_js_to_fragment(fragment, 'ProblemBlockEditor') shim_xmodule_js(fragment, 'MarkdownEditingDescriptor') return fragment diff --git a/xmodule/lti_block.py b/xmodule/lti_block.py index 55f940b381c2..e7c173075b4e 100644 --- a/xmodule/lti_block.py +++ b/xmodule/lti_block.py @@ -84,7 +84,7 @@ ) from xmodule.lti_2_util import LTI20BlockMixin, LTIError from xmodule.raw_block import EmptyDataRawMixin -from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_sass_to_fragment +from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_css_to_fragment from xmodule.xml_block import XmlMixin from xmodule.x_module import ( ResourceTemplates, @@ -524,7 +524,7 @@ def student_view(self, _context): """ fragment = Fragment() fragment.add_content(self.runtime.service(self, 'mako').render_lms_template('lti.html', self.get_context())) - add_sass_to_fragment(fragment, 'LTIBlockDisplay.scss') + add_css_to_fragment(fragment, 'LTIBlockDisplay.css') add_webpack_js_to_fragment(fragment, 'LTIBlockDisplay') shim_xmodule_js(fragment, 'LTI') return fragment diff --git a/xmodule/modulestore/search.py b/xmodule/modulestore/search.py index 60317039821a..fc065078968b 100644 --- a/xmodule/modulestore/search.py +++ b/xmodule/modulestore/search.py @@ -12,7 +12,7 @@ LOGGER = getLogger(__name__) -def path_to_location(modulestore, usage_key, request=None, full_path=False): +def path_to_location(modulestore, usage_key, request=None, full_path=False, branch_type=None): ''' Try to find a course_id/chapter/section[/position] path to location in modulestore. The courseware insists that the first level in the course is @@ -82,46 +82,47 @@ def find_path_to_course(): queue.append((parent, newpath)) with modulestore.bulk_operations(usage_key.course_key): - if not modulestore.has_item(usage_key): - raise ItemNotFoundError(usage_key) - - path = find_path_to_course() - if path is None: - raise NoPathToItem(usage_key) - - if full_path: - return path - - n = len(path) - course_id = path[0].course_key - # pull out the location names - chapter = path[1].block_id if n > 1 else None - section = path[2].block_id if n > 2 else None - vertical = path[3].block_id if n > 3 else None - # Figure out the position - position = None - - # This block of code will find the position of a block within a nested tree - # of blocks. If a problem is on tab 2 of a sequence that's on tab 3 of a - # sequence, the resulting position is 3_2. However, no positional blocks - # (e.g. sequential) currently deal with this form of representing nested - # positions. This needs to happen before jumping to a block nested in more - # than one positional block will work. - - if n > 3: - position_list = [] - for path_index in range(2, n - 1): - category = path[path_index].block_type - if category == 'sequential': - section_desc = modulestore.get_item(path[path_index]) - # this calls get_children rather than just children b/c old mongo includes private children - # in children but not in get_children - child_locs = get_child_locations(section_desc, request, course_id) - # positions are 1-indexed, and should be strings to be consistent with - # url parsing. - if path[path_index + 1] in child_locs: - position_list.append(str(child_locs.index(path[path_index + 1]) + 1)) - position = "_".join(position_list) + with modulestore.branch_setting(branch_type, usage_key.course_key): + if not modulestore.has_item(usage_key): + raise ItemNotFoundError(usage_key) + + path = find_path_to_course() + if path is None: + raise NoPathToItem(usage_key) + + if full_path: + return path + + n = len(path) + course_id = path[0].course_key + # pull out the location names + chapter = path[1].block_id if n > 1 else None + section = path[2].block_id if n > 2 else None + vertical = path[3].block_id if n > 3 else None + # Figure out the position + position = None + + # This block of code will find the position of a block within a nested tree + # of blocks. If a problem is on tab 2 of a sequence that's on tab 3 of a + # sequence, the resulting position is 3_2. However, no positional blocks + # (e.g. sequential) currently deal with this form of representing nested + # positions. This needs to happen before jumping to a block nested in more + # than one positional block will work. + + if n > 3: + position_list = [] + for path_index in range(2, n - 1): + category = path[path_index].block_type + if category == 'sequential': + section_desc = modulestore.get_item(path[path_index]) + # this calls get_children rather than just children b/c old mongo includes private children + # in children but not in get_children + child_locs = get_child_locations(section_desc, request, course_id) + # positions are 1-indexed, and should be strings to be consistent with + # url parsing. + if path[path_index + 1] in child_locs: + position_list.append(str(child_locs.index(path[path_index + 1]) + 1)) + position = "_".join(position_list) return (course_id, chapter, section, vertical, position, path[-1]) diff --git a/xmodule/modulestore/tests/test_mixed_modulestore.py b/xmodule/modulestore/tests/test_mixed_modulestore.py index 8adbfcb911a4..0928ab253b9c 100644 --- a/xmodule/modulestore/tests/test_mixed_modulestore.py +++ b/xmodule/modulestore/tests/test_mixed_modulestore.py @@ -1752,7 +1752,7 @@ def test_path_to_location(self, default_ms, num_mysql, num_finds, num_sends): for location, expected in should_work: # each iteration has different find count, pop this iter's find count with check_mongo_calls(num_finds.pop(0), num_sends), self.assertNumQueries(num_mysql.pop(0)): - path = path_to_location(self.store, location) + path = path_to_location(self.store, location, branch_type=ModuleStoreEnum.Branch.published_only) assert path == expected not_found = ( diff --git a/xmodule/seq_block.py b/xmodule/seq_block.py index 6d1a8c59adeb..1b94f47d898c 100644 --- a/xmodule/seq_block.py +++ b/xmodule/seq_block.py @@ -23,7 +23,7 @@ from xblock.fields import Boolean, Integer, List, Scope, String from edx_toggles.toggles import WaffleFlag, SettingDictToggle -from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_sass_to_fragment +from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_css_to_fragment from xmodule.x_module import ( ResourceTemplates, shim_xmodule_js, @@ -459,7 +459,7 @@ def student_view(self, context): banner_text, special_html = special_html_view if special_html and not masquerading_as_specific_student: fragment = Fragment(special_html) - add_sass_to_fragment(fragment, 'SequenceBlockDisplay.scss') + add_css_to_fragment(fragment, 'SequenceBlockDisplay.css') add_webpack_js_to_fragment(fragment, 'SequenceBlockDisplay') shim_xmodule_js(fragment, 'Sequence') return fragment @@ -601,7 +601,7 @@ def _student_or_public_view(self, context, prereq_met, prereq_meta_info, banner_ self._capture_full_seq_item_metrics(children) self._capture_current_unit_metrics(children) - add_sass_to_fragment(fragment, 'SequenceBlockDisplay.scss') + add_css_to_fragment(fragment, 'SequenceBlockDisplay.css') add_webpack_js_to_fragment(fragment, 'SequenceBlockDisplay') shim_xmodule_js(fragment, 'Sequence') return fragment diff --git a/xmodule/static/css-builtin-blocks/AnnotatableBlockDisplay.css b/xmodule/static/css-builtin-blocks/AnnotatableBlockDisplay.css index 45b395ec66ab..804d84a7717a 100644 --- a/xmodule/static/css-builtin-blocks/AnnotatableBlockDisplay.css +++ b/xmodule/static/css-builtin-blocks/AnnotatableBlockDisplay.css @@ -1,16 +1,5 @@ @import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700"); -.xmodule_display.xmodule_AnnotatableBlock { - /* TODO: move top-level variables to a common _variables.scss. - * NOTE: These variables were only added here because when this was integrated with the CMS, - * SASS compilation errors were triggered because the CMS didn't have the same variables defined - * that the LMS did, so the quick fix was to localize the LMS variables not shared by the CMS. - * -Abarrett and Vshnayder - */ - /* stylelint-disable-line */ - /* stylelint-disable-line */ -} - .xmodule_display.xmodule_AnnotatableBlock .annotatable-wrapper { position: relative; } diff --git a/xmodule/static/css-builtin-blocks/CustomTagBlockEditor.css b/xmodule/static/css-builtin-blocks/CustomTagBlockEditor.css new file mode 100644 index 000000000000..320b6a379cc3 --- /dev/null +++ b/xmodule/static/css-builtin-blocks/CustomTagBlockEditor.css @@ -0,0 +1,5 @@ +.xmodule_edit.xmodule_CustomTagBlock .CodeMirror { + background: #fff; + font-size: 13px; + color: #3c3c3c; +} diff --git a/xmodule/static/css-builtin-blocks/HtmlBlockDisplay.css b/xmodule/static/css-builtin-blocks/HtmlBlockDisplay.css index b89bb3c8c7ed..0e7a20017092 100644 --- a/xmodule/static/css-builtin-blocks/HtmlBlockDisplay.css +++ b/xmodule/static/css-builtin-blocks/HtmlBlockDisplay.css @@ -1,13 +1,5 @@ @import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700"); -.xmodule_display.xmodule_AboutBlock, -.xmodule_display.xmodule_CourseInfoBlock, -.xmodule_display.xmodule_HtmlBlock, -.xmodule_display.xmodule_StaticTabBlock { - /* stylelint-disable-line */ - /* stylelint-disable-line */ -} - .xmodule_display.xmodule_AboutBlock *, .xmodule_display.xmodule_CourseInfoBlock *, .xmodule_display.xmodule_HtmlBlock *, @@ -138,18 +130,18 @@ font-weight: bold; } -.xmodule_display.xmodule_AboutBlock p + p, -.xmodule_display.xmodule_AboutBlock ul + p, -.xmodule_display.xmodule_AboutBlock ol + p, -.xmodule_display.xmodule_CourseInfoBlock p + p, -.xmodule_display.xmodule_CourseInfoBlock ul + p, -.xmodule_display.xmodule_CourseInfoBlock ol + p, -.xmodule_display.xmodule_HtmlBlock p + p, -.xmodule_display.xmodule_HtmlBlock ul + p, -.xmodule_display.xmodule_HtmlBlock ol + p, -.xmodule_display.xmodule_StaticTabBlock p + p, -.xmodule_display.xmodule_StaticTabBlock ul + p, -.xmodule_display.xmodule_StaticTabBlock ol + p { +.xmodule_display.xmodule_AboutBlock p+p, +.xmodule_display.xmodule_AboutBlock ul+p, +.xmodule_display.xmodule_AboutBlock ol+p, +.xmodule_display.xmodule_CourseInfoBlock p+p, +.xmodule_display.xmodule_CourseInfoBlock ul+p, +.xmodule_display.xmodule_CourseInfoBlock ol+p, +.xmodule_display.xmodule_HtmlBlock p+p, +.xmodule_display.xmodule_HtmlBlock ul+p, +.xmodule_display.xmodule_HtmlBlock ol+p, +.xmodule_display.xmodule_StaticTabBlock p+p, +.xmodule_display.xmodule_StaticTabBlock ul+p, +.xmodule_display.xmodule_StaticTabBlock ol+p { margin-top: var(--baseline); } @@ -198,7 +190,11 @@ list-style: disc outside none; } -.xmodule_display.xmodule_AboutBlock a:link, .xmodule_display.xmodule_AboutBlock a:visited, .xmodule_display.xmodule_AboutBlock a:hover, .xmodule_display.xmodule_AboutBlock a:active, .xmodule_display.xmodule_AboutBlock a:focus, +.xmodule_display.xmodule_AboutBlock a:link, +.xmodule_display.xmodule_AboutBlock a:visited, +.xmodule_display.xmodule_AboutBlock a:hover, +.xmodule_display.xmodule_AboutBlock a:active, +.xmodule_display.xmodule_AboutBlock a:focus, .xmodule_display.xmodule_CourseInfoBlock a:link, .xmodule_display.xmodule_CourseInfoBlock a:visited, .xmodule_display.xmodule_CourseInfoBlock a:hover, @@ -222,6 +218,7 @@ .xmodule_display.xmodule_HtmlBlock img, .xmodule_display.xmodule_StaticTabBlock img { max-width: 100%; + height: auto; } .xmodule_display.xmodule_AboutBlock pre, diff --git a/xmodule/static/css-builtin-blocks/HtmlBlockEditor.css b/xmodule/static/css-builtin-blocks/HtmlBlockEditor.css index feae8a0034dd..c7ef3b7ec147 100644 --- a/xmodule/static/css-builtin-blocks/HtmlBlockEditor.css +++ b/xmodule/static/css-builtin-blocks/HtmlBlockEditor.css @@ -1,10 +1,5 @@ @import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700"); -.xmodule_edit.xmodule_AboutBlock, -.xmodule_edit.xmodule_CourseInfoBlock, -.xmodule_edit.xmodule_HtmlBlock, -.xmodule_edit.xmodule_StaticTabBlock { -} .xmodule_edit.xmodule_AboutBlock .ui-col-wide, .xmodule_edit.xmodule_CourseInfoBlock .ui-col-wide, @@ -118,7 +113,8 @@ height: 21px; } -.xmodule_edit.xmodule_AboutBlock .editor .editor-bar button:hover, .xmodule_edit.xmodule_AboutBlock .editor .editor-bar button:focus, +.xmodule_edit.xmodule_AboutBlock .editor .editor-bar button:hover, +.xmodule_edit.xmodule_AboutBlock .editor .editor-bar button:focus, .xmodule_edit.xmodule_CourseInfoBlock .editor .editor-bar button:hover, .xmodule_edit.xmodule_CourseInfoBlock .editor .editor-bar button:focus, .xmodule_edit.xmodule_HtmlBlock .editor .editor-bar button:hover, diff --git a/xmodule/static/css-builtin-blocks/LTIBlockDisplay.css b/xmodule/static/css-builtin-blocks/LTIBlockDisplay.css new file mode 100644 index 000000000000..5ca27a4c4a0a --- /dev/null +++ b/xmodule/static/css-builtin-blocks/LTIBlockDisplay.css @@ -0,0 +1,56 @@ +@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700"); + + +.xmodule_display.xmodule_LTIBlock h2.problem-header { + display: inline-block; +} + +.xmodule_display.xmodule_LTIBlock div.problem-progress { + display: inline-block; + padding-left: calc((var(--baseline) / 4)); + color: #666; + font-weight: 100; + font-size: 1em; +} + +.xmodule_display.xmodule_LTIBlock div.lti { + margin: 0 auto; +} + +.xmodule_display.xmodule_LTIBlock div.lti .wrapper-lti-link { + font-size: 14px; + background-color: var(--sidebar-color); + padding: var(--baseline); +} + +.xmodule_display.xmodule_LTIBlock div.lti .wrapper-lti-link .lti-link { + margin-bottom: 0; + text-align: right; +} + +.xmodule_display.xmodule_LTIBlock div.lti .wrapper-lti-link .lti-link .link_lti_new_window { + font-size: 13px; + line-height: 20.72px; +} + +.xmodule_display.xmodule_LTIBlock div.lti form.ltiLaunchForm { + display: none; +} + +.xmodule_display.xmodule_LTIBlock div.lti iframe.ltiLaunchFrame { + width: 100%; + height: 800px; + display: block; + border: 0px; +} + +.xmodule_display.xmodule_LTIBlock div.lti h4.problem-feedback-label { + font-weight: 100; + font-size: 1em; + font-family: "Source Sans", "Open Sans", Verdana, Geneva, sans-serif, sans-serif; +} + +.xmodule_display.xmodule_LTIBlock div.lti div.problem-feedback { + margin-top: calc((var(--baseline) / 4)); + margin-bottom: calc((var(--baseline) / 4)); +} diff --git a/xmodule/static/css-builtin-blocks/PollBlockDisplay.css b/xmodule/static/css-builtin-blocks/PollBlockDisplay.css index f617dc960d38..cf9058281dd4 100644 --- a/xmodule/static/css-builtin-blocks/PollBlockDisplay.css +++ b/xmodule/static/css-builtin-blocks/PollBlockDisplay.css @@ -1,10 +1,5 @@ @import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700"); -.xmodule_display.xmodule_PollBlock { - /* stylelint-disable-line */ - /* stylelint-disable-line */ -} - @media print { .xmodule_display.xmodule_PollBlock div.poll_question { display: block; @@ -12,7 +7,8 @@ padding: 0; } - .xmodule_display.xmodule_PollBlock div.poll_question canvas, .xmodule_display.xmodule_PollBlock div.poll_question img { + .xmodule_display.xmodule_PollBlock div.poll_question canvas, + .xmodule_display.xmodule_PollBlock div.poll_question img { page-break-inside: avoid; } } diff --git a/xmodule/static/css-builtin-blocks/ProblemBlockDisplay.css b/xmodule/static/css-builtin-blocks/ProblemBlockDisplay.css new file mode 100644 index 000000000000..3cd20ab7608e --- /dev/null +++ b/xmodule/static/css-builtin-blocks/ProblemBlockDisplay.css @@ -0,0 +1,1738 @@ +@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700"); + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.incorrect .status .status-icon::after, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.incorrect .status .status-icon::after, +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.partially-correct .status .status-icon::after, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.partially-correct .status .status-icon::after, +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.correct .status .status-icon::after, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.correct .status .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .partially-correct .status-icon::after { + font-family: FontAwesome; + -webkit-font-smoothing: antialiased; + display: inline-block; + speak: none; +} + +.xmodule_display.xmodule_ProblemBlock h2 { + margin-top: 0; + margin-bottom: calc((var(--baseline) * 0.75)); +} + +.xmodule_display.xmodule_ProblemBlock h2.problem-header { + display: inline-block; +} + +.xmodule_display.xmodule_ProblemBlock h2.problem-header section.staff { + margin-top: calc((var(--baseline) * 1.5)); + font-size: 80%; +} + +@media print { + .xmodule_display.xmodule_ProblemBlock h2 { + display: block; + width: auto; + border-right: 0; + } +} + +.xmodule_display.xmodule_ProblemBlock .explanation-title { + font-weight: bold; +} + +.xmodule_display.xmodule_ProblemBlock .feedback-hint-incorrect, +.xmodule_display.xmodule_ProblemBlock .feedback-hint-partially-correct, +.xmodule_display.xmodule_ProblemBlock .feedback-hint-correct { + margin-top: calc((var(--baseline) / 4)); +} + +.xmodule_display.xmodule_ProblemBlock .feedback-hint-incorrect .icon, +.xmodule_display.xmodule_ProblemBlock .feedback-hint-partially-correct .icon, +.xmodule_display.xmodule_ProblemBlock .feedback-hint-correct .icon { + margin-right: calc((var(--baseline) / 4)); +} + +.xmodule_display.xmodule_ProblemBlock .feedback-hint-incorrect .icon { + color: var(--incorrect); +} + +.xmodule_display.xmodule_ProblemBlock .feedback-hint-partially-correct .icon, +.xmodule_display.xmodule_ProblemBlock .feedback-hint-correct .icon { + color: var(--correct); +} + +.xmodule_display.xmodule_ProblemBlock .feedback-hint-text { + color: #646464; +} + +.xmodule_display.xmodule_ProblemBlock .problem-hint { + margin-bottom: 20px; + width: 100%; +} + +.xmodule_display.xmodule_ProblemBlock .hint-label { + display: inline-block; + padding-right: 0.5em; +} + +.xmodule_display.xmodule_ProblemBlock .hint-text { + display: inline-block; +} + +.xmodule_display.xmodule_ProblemBlock .feedback-hint-multi .hint-text { + display: block; +} + +.xmodule_display.xmodule_ProblemBlock iframe[seamless] { + overflow: hidden; + padding: 0; + border: 0 none transparent; + background-color: transparent; +} + +.xmodule_display.xmodule_ProblemBlock .inline-error { + color: var(--error-color-dark); +} + +.xmodule_display.xmodule_ProblemBlock div.problem-progress { + display: inline-block; + color: var(--gray-d1); + font-size: 0.875em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem { + padding-top: var(--baseline); +} + +@media print { + .xmodule_display.xmodule_ProblemBlock div.problem { + display: block; + padding: 0; + width: auto; + } + + .xmodule_display.xmodule_ProblemBlock div.problem canvas, + .xmodule_display.xmodule_ProblemBlock div.problem img { + page-break-inside: avoid; + } +} + +.xmodule_display.xmodule_ProblemBlock div.problem input.math { + direction: ltr; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .inline { + display: inline; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .inline+p { + margin-top: var(--baseline); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .question-description { + color: var(--gray-d1); + font-size: var(--small-font-size); +} + +.xmodule_display.xmodule_ProblemBlock div.problem form>label, +.xmodule_display.xmodule_ProblemBlock div.problem .problem-group-label { + display: block; + margin-bottom: var(--baseline); + font: inherit; + color: inherit; + -webkit-font-smoothing: initial; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .problem-group-label+.question-description { + margin-top: calc(-1 * var(--baseline)); +} + +.xmodule_display.xmodule_ProblemBlock .wrapper-problem-response+.wrapper-problem-response, +.xmodule_display.xmodule_ProblemBlock .wrapper-problem-response+p { + margin-top: calc((var(--baseline) * 1.5)); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup { + min-width: 100px; + width: auto !important; + width: 100px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup:after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup:after { + content: ""; + display: table; + clear: both; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup label, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup label { + box-sizing: border-box; + display: inline-block; + clear: both; + margin-bottom: calc((var(--baseline) / 2)); + border: 2px solid var(--gray-l4); + border-radius: 3px; + padding: calc((var(--baseline) / 2)); + width: 100%; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup label::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup label::after { + margin-left: calc((var(--baseline) * 0.75)); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup .indicator-container, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .indicator-container { + min-height: 1px; + width: 25px; + display: inline-block; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup fieldset, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup fieldset { + box-sizing: border-box; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input[type="radio"], +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input[type="radio"], +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input[type="checkbox"], +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input[type="checkbox"] { + margin: calc((var(--baseline) / 4)); + margin-right: calc((var(--baseline) / 2)); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label { + border: 2px solid var(--blue); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label.choicegroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicegroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+label.choicetextgroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicetextgroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+section.choicetextgroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+section.choicetextgroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label.choicegroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicegroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+label.choicetextgroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicetextgroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+section.choicetextgroup_correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+section.choicetextgroup_correct { + border: 2px solid var(--correct); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label.choicegroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicegroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+label.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+section.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+section.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label.choicegroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicegroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+label.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+section.choicetextgroup_correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+section.choicetextgroup_correct .status-icon::after { + color: var(--correct); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label.choicegroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicegroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+label.choicetextgroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicetextgroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+section.choicetextgroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+section.choicetextgroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label.choicegroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicegroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+label.choicetextgroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicetextgroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+section.choicetextgroup_partially-correct, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+section.choicetextgroup_partially-correct { + border: 2px solid var(--partially-correct); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label.choicegroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicegroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+label.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+section.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+section.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label.choicegroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicegroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+label.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+section.choicetextgroup_partially-correct .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+section.choicetextgroup_partially-correct .status-icon::after { + color: var(--partially-correct); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label.choicegroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicegroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+label.choicetextgroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicetextgroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+section.choicetextgroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+section.choicetextgroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label.choicegroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicegroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+label.choicetextgroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicetextgroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+section.choicetextgroup_incorrect, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+section.choicetextgroup_incorrect { + border: 2px solid var(--incorrect); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label.choicegroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicegroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+label.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+section.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+section.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label.choicegroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicegroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+label.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+section.choicetextgroup_incorrect .status-icon::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+section.choicetextgroup_incorrect .status-icon::after { + color: var(--incorrect); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input+label.choicegroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicegroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+label.choicetextgroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+label.choicetextgroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input+section.choicetextgroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input+section.choicetextgroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:focus+label.choicegroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicegroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+label.choicetextgroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+label.choicetextgroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:focus+section.choicetextgroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:focus+section.choicetextgroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input:hover+label.choicegroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicegroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+label.choicetextgroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+label.choicetextgroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup .choicegroup input:hover+section.choicetextgroup_submitted, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input:hover+section.choicetextgroup_submitted { + border: 2px solid var(--submitted); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup .field { + position: relative; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup label { + padding: calc((var(--baseline) / 2)); + padding-left: calc((var(--baseline) * 2.3)); + position: relative; + font-size: var(--base-font-size); + line-height: normal; + cursor: pointer; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input[type="radio"], +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup input[type="checkbox"] { + left: 0.5625em; + position: absolute; + top: 0.35em; + width: calc(var(--baseline) * 1.1); + height: calc(var(--baseline) * 1.1); + z-index: 1; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup legend { + margin-bottom: var(--baseline); + max-width: 100%; + white-space: normal; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicegroup legend+.question-description { + margin-top: calc(-1 * var(--baseline)); + max-width: 100%; + white-space: normal; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container { + margin-left: calc((var(--baseline) * 0.75)); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status { + width: var(--baseline); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.correct .status-icon::after { + color: var(--correct); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.partially-correct .status-icon::after { + color: var(--partially-correct); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.incorrect .status-icon::after { + color: var(--incorrect); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.submitted .status-icon, +.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.unsubmitted .status-icon, +.xmodule_display.xmodule_ProblemBlock div.problem .indicator-container .status.unanswered .status-icon { + content: ''; +} + +.xmodule_display.xmodule_ProblemBlock div.problem ol.enumerate li::before { + display: block; + visibility: hidden; + height: 0; + content: " "; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .solution-span>span { + margin: var(--baseline) 0; + display: block; + position: relative; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .solution-span>span:empty { + display: none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .targeted-feedback-span>span { + display: block; + position: relative; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .targeted-feedback-span>span:empty { + display: none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div p.answer { + margin-top: -2px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div p span.clarification i { + font-style: normal; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div p span.clarification i:hover { + color: var(--blue); +} + +.xmodule_display.xmodule_ProblemBlock div.problem div.correct input, +.xmodule_display.xmodule_ProblemBlock div.problem div.ui-icon-check input { + border-color: var(--correct); +} + +.xmodule_display.xmodule_ProblemBlock div.problem div.partially-correct input, +.xmodule_display.xmodule_ProblemBlock div.problem div.ui-icon-check input { + border-color: var(--partially-correct); +} + +.xmodule_display.xmodule_ProblemBlock div.problem div.processing input { + border-color: #aaa; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div.ui-icon-close input { + border-color: var(--incorrect); +} + +.xmodule_display.xmodule_ProblemBlock div.problem div.incorrect input, +.xmodule_display.xmodule_ProblemBlock div.problem div.incomplete input { + border-color: var(--incorrect); +} + +.xmodule_display.xmodule_ProblemBlock div.problem div.submitted input, +.xmodule_display.xmodule_ProblemBlock div.problem div.ui-icon-check input { + border-color: var(--submitted); +} + +.xmodule_display.xmodule_ProblemBlock div.problem div p.answer { + display: inline-block; + margin-top: calc((var(--baseline) / 2)); + margin-bottom: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div p.answer::before { + display: inline; + content: "Answer: "; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div p.answer:empty::before { + display: none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div div.equation { + clear: both; + margin-top: 3px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div div.equation .MathJax_Display { + width: auto; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div div.equation img.loading { + padding-left: calc((var(--baseline) / 2)); + display: inline-block; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div div.equation span { + margin-bottom: 0; + display: inline-block; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div div.equation span.MathJax_CHTML, +.xmodule_display.xmodule_ProblemBlock div.problem div div.equation span.MathJax, +.xmodule_display.xmodule_ProblemBlock div.problem div div.equation span.MathJax_SVG { + padding: 6px; + min-width: 30px; + border: 1px solid #e3e3e3; + border-radius: 4px; + background: #f1f1f1; +} + +@media print { + .xmodule_display.xmodule_ProblemBlock div.problem div [id^='display'].equation { + display: none; + } +} + +.xmodule_display.xmodule_ProblemBlock div.problem div span.ui-icon-bullet { + display: inline-block; + position: relative; + top: 4px; + width: 14px; + height: 14px; + background: url("var(--static-path)/images/unanswered-icon.png") center center no-repeat; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div span.processing, +.xmodule_display.xmodule_ProblemBlock div.problem div span.ui-icon-processing { + display: inline-block; + position: relative; + top: 6px; + width: 25px; + height: 20px; + background: url("var(--static-path)/images/spinner.gif") center center no-repeat; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div span.ui-icon-check { + display: inline-block; + position: relative; + top: 3px; + width: 25px; + height: 20px; + background: url("var(--static-path)/images/correct-icon.png") center center no-repeat; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div span.incomplete, +.xmodule_display.xmodule_ProblemBlock div.problem div span.ui-icon-close { + display: inline-block; + position: relative; + top: 3px; + width: 20px; + height: 20px; + background: url("var(--static-path)/images/incorrect-icon.png") center center no-repeat; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .reload { + float: right; + margin: calc((var(--baseline) / 2)); +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .grader-status { + margin: calc(var(--baseline) / 2) 0; + padding: calc(var(--baseline) / 2); + border-radius: 5px; + background: var(--gray-l6); +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .grader-status:after { + content: ""; + display: table; + clear: both; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .grader-status span { + display: block; + float: left; + overflow: hidden; + margin: -7px 7px 0 0; + text-indent: -9999px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .grader-status .grading { + margin: 0px 7px 0 0; + padding-left: 25px; + background: url("var(--static-path)/images/info-icon.png") left center no-repeat; + text-indent: 0px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .grader-status p { + float: left; + margin-bottom: 0; + text-transform: capitalize; + line-height: 20px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .grader-status.file { + margin-top: var(--baseline); + padding: var(--baseline) 0 0 0; + border: 0; + border-top: 1px solid #eee; + background: var(--white); +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .grader-status.file p.debug { + display: none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .grader-status.file input { + float: left; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .evaluation p { + margin-bottom: calc((var(--baseline) / 5)); +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .feedback-on-feedback { + margin-right: var(--baseline); + height: 100px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .evaluation-response header { + text-align: right; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .evaluation-response header a { + font-size: .85em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .evaluation-scoring .scoring-list { + margin-left: 3px; + list-style-type: none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .evaluation-scoring .scoring-list li { + display: inline; + margin-left: 50px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .evaluation-scoring .scoring-list li:first-child { + margin-left: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .evaluation-scoring .scoring-list li label { + font-size: .9em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div .submit-message-container { + margin: var(--baseline) 0px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div.inline>span { + display: inline; +} + +.xmodule_display.xmodule_ProblemBlock div.problem ul { + margin-bottom: lh(); + margin-left: .75em; + margin-left: .75rem; + list-style: disc outside none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem ol { + margin-bottom: lh(); + margin-left: .75em; + margin-left: .75rem; + list-style: decimal outside none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem dl { + line-height: 1.4em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem dl dd { + margin-bottom: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem dd { + margin-left: .5em; + margin-left: .5rem; +} + +.xmodule_display.xmodule_ProblemBlock div.problem li { + margin-bottom: lh(0.5); + line-height: 1.4em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem li:last-child { + margin-bottom: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem p { + margin-bottom: lh(); +} + +.xmodule_display.xmodule_ProblemBlock div.problem table { + margin: lh() 0; + border-collapse: collapse; + table-layout: auto; +} + +.xmodule_display.xmodule_ProblemBlock div.problem table td.cont-justified-left, +.xmodule_display.xmodule_ProblemBlock div.problem table th.cont-justified-left { + text-align: left !important; +} + +.xmodule_display.xmodule_ProblemBlock div.problem table td.cont-justified-right, +.xmodule_display.xmodule_ProblemBlock div.problem table th.cont-justified-right { + text-align: right !important; +} + +.xmodule_display.xmodule_ProblemBlock div.problem table td.cont-justified-center, +.xmodule_display.xmodule_ProblemBlock div.problem table th.cont-justified-center { + text-align: center !important; +} + +.xmodule_display.xmodule_ProblemBlock div.problem table th { + text-align: left; +} + +.xmodule_display.xmodule_ProblemBlock div.problem table td { + text-align: left; +} + +.xmodule_display.xmodule_ProblemBlock div.problem table caption, +.xmodule_display.xmodule_ProblemBlock div.problem table th, +.xmodule_display.xmodule_ProblemBlock div.problem table td { + padding: .25em .75em .25em 0; + padding: .25rem .75rem .25rem 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem table caption { + margin-bottom: .75em; + margin-bottom: .75rem; + padding: .75em 0; + padding: .75rem 0; + background: #f1f1f1; +} + +.xmodule_display.xmodule_ProblemBlock div.problem table tr, +.xmodule_display.xmodule_ProblemBlock div.problem table td, +.xmodule_display.xmodule_ProblemBlock div.problem table th { + vertical-align: middle; +} + +.xmodule_display.xmodule_ProblemBlock div.problem code { + margin: 0 2px; + padding: 0px 5px; + border: 1px solid #eaeaea; + border-radius: 3px; + background-color: var(--gray-l6); + white-space: nowrap; + font-size: .9em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem pre { + overflow: auto; + padding: 6px calc(var(--baseline) / 2); + border: 1px solid var(--gray-l3); + border-radius: 3px; + background-color: var(--gray-l6); + font-size: .9em; + line-height: 1.4; +} + +.xmodule_display.xmodule_ProblemBlock div.problem pre>code { + margin: 0; + padding: 0; + border: none; + background: transparent; + white-space: pre; +} + +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline input, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput input { + box-sizing: border-box; + border: 2px solid var(--gray-l4); + border-radius: 3px; + min-width: 160px; + height: 46px; +} + +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline .status, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput .status { + display: inline-block; + margin-top: calc((var(--baseline) / 2)); + background: none; +} + +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.incorrect input, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.incorrect input { + border: 2px solid var(--incorrect); +} + +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.incorrect .status .status-icon::after, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.incorrect .status .status-icon::after { + color: var(--incorrect); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.partially-correct input, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.partially-correct input { + border: 2px solid var(--partially-correct); +} + +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.partially-correct .status .status-icon::after, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.partially-correct .status .status-icon::after { + color: var(--partially-correct); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.correct input, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.correct input { + border: 2px solid var(--correct); +} + +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.correct .status .status-icon::after, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.correct .status .status-icon::after { + color: var(--correct); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.submitted input, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.submitted input { + border: 2px solid var(--submitted); +} + +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.submitted .status, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.submitted .status { + content: ''; +} + +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.unanswered input, +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.unsubmitted input, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.unanswered input, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.unsubmitted input { + border: 2px solid var(--gray-l4); +} + +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.unanswered .status .status-icon::after, +.xmodule_display.xmodule_ProblemBlock .problem .capa_inputtype.textline>.unsubmitted .status .status-icon::after, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.unanswered .status .status-icon::after, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>.unsubmitted .status .status-icon::after { + content: ''; + display: inline-block; +} + +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.formulaequationinput>div input { + direction: ltr; + text-align: left; +} + +.xmodule_display.xmodule_ProblemBlock .problem .trailing_text { + margin-right: calc((var(--baseline) / 2)); + display: inline-block; +} + +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.option-input { + margin: 0 0 0 0 !important; +} + +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.option-input .indicator-container { + display: inline-block; +} + +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.option-input .indicator-container .status.correct::after, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.option-input .indicator-container .status.partially-correct::after, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.option-input .indicator-container .status.incorrect::after, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.option-input .indicator-container .status.submitted::after, +.xmodule_display.xmodule_ProblemBlock .problem .inputtype.option-input .indicator-container .status.unanswered::after { + margin-left: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .CodeMirror { + border: 1px solid black; + font-size: 14px; + line-height: 18px; + resize: none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .CodeMirror .cm-tab { + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAGFJREFUSMft1LsRQFAQheHPowAKoACx3IgEKtaEHujDjORSgWTH/ZOdnZOcM/sgk/kFFWY0qV8foQwS4MKBCS3qR6ixBJvElOobYAtivseIE120FaowJPN75GMu8j/LfMwNjh4HUpwg4LUAAAAASUVORK5CYII=); + background-position: right; + background-repeat: no-repeat; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .CodeMirror pre { + overflow: hidden; + margin: 0; + padding: 0; + border-width: 0; + border-radius: 0; + background: transparent; + white-space: pre; + word-wrap: normal; + font-size: inherit; + font-family: inherit; + resize: none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .CodeMirror pre.CodeMirror-cursor { + position: absolute; + visibility: hidden; + width: 0; + border-right: none; + border-left: 1px solid var(--black); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .CodeMirror-focused pre.CodeMirror-cursor { + visibility: visible; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .CodeMirror-code pre { + width: -webkit-fit-content; + width: -moz-fit-content; + width: fit-content; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .CodeMirror-scroll { + margin-right: 0px; +} + +.xmodule_display.xmodule_ProblemBlock .capa-message { + display: inline-block; + color: var(--gray-d1); + -webkit-font-smoothing: antialiased; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .action { + min-height: var(--baseline); + width: 100%; + display: flex; + display: -ms-flexbox; + -ms-flex-align: start; + flex-direction: row; + align-items: center; + flex-wrap: wrap; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .action .problem-action-buttons-wrapper { + display: inline-flex; + justify-content: flex-end; + width: 100%; + padding-bottom: var(--baseline); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .action .problem-action-button-wrapper { + border-right: 1px solid var(--gray-300); + padding: 0 13px; + display: inline-block; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .action .problem-action-button-wrapper:last-child { + border: none; + padding-right: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .action .problem-action-btn { + border: none; + max-width: 110px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .action .problem-action-btn:hover, +.xmodule_display.xmodule_ProblemBlock div.problem .action .problem-action-btn:focus, +.xmodule_display.xmodule_ProblemBlock div.problem .action .problem-action-btn:active { + color: var(--primary) !important; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .action .problem-action-btn .icon { + margin-bottom: calc(var(--baseline) / 10); + display: block; +} + +@media print { + .xmodule_display.xmodule_ProblemBlock div.problem .action .problem-action-btn { + display: none; + } +} + +.xmodule_display.xmodule_ProblemBlock div.problem .action .submit-attempt-container { + padding-bottom: var(--baseline); + flex-grow: 1; + display: flex; + align-items: center; +} + +@media (max-width: var(--bp-screen-lg)) { + .xmodule_display.xmodule_ProblemBlock div.problem .action .submit-attempt-container { + max-width: 100%; + padding-bottom: var(--baseline); + } +} + +.xmodule_display.xmodule_ProblemBlock div.problem .action .submit-attempt-container .submit { + margin-right: calc((var(--baseline) / 2)); + float: left; + white-space: nowrap; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .action .submit-attempt-container .submit-cta-description { + color: var(--primary); + font-size: small; + padding-right: calc(var(--baseline) / 2); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .action .submit-attempt-container .submit-cta-link-button { + color: var(--primary); + padding-right: calc(var(--baseline) / 4); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .action .submission-feedback { + margin-right: calc((var(--baseline) / 2)); + margin-top: calc(var(--baseline) / 2); + display: inline-block; + color: var(--gray-d1); + font-size: var(--medium-font-size); + -webkit-font-smoothing: antialiased; + vertical-align: middle; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .action .submission-feedback.cta-enabled { + margin-top: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem hr { + float: none; + clear: both; + margin: 0 0 .75rem; + width: 100%; + height: 1px; + border: none; + background: #ddd; + color: #ddd; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .hidden { + display: none; + visibility: hidden; +} + +.xmodule_display.xmodule_ProblemBlock div.problem var (--all-text-inputs) { + display: inline; + width: auto; +} + +.xmodule_display.xmodule_ProblemBlock div.problem center { + display: block; + margin: lh() 0; + padding: lh(); + border: 1px solid var(--gray-l3); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .message { + font-size: inherit; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .detailed-solution>p { + margin: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .detailed-solution>p:first-child { + margin-bottom: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .detailed-targeted-feedback>p, +.xmodule_display.xmodule_ProblemBlock div.problem .detailed-targeted-feedback-partially-correct>p, +.xmodule_display.xmodule_ProblemBlock div.problem .detailed-targeted-feedback-correct>p { + margin: 0; + font-weight: normal; +} + +.xmodule_display.xmodule_ProblemBlock div.problem div.capa_alert { + margin-top: var(--baseline); + padding: 8px 12px; + border: 1px solid var(--warning-color); + border-radius: 3px; + background: var(--warning-color-accent); + font-size: 0.9em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification { + float: left; + margin-top: calc(var(--baseline) / 2); + padding: calc((var(--baseline) / 2.5)) calc((var(--baseline) / 2)) calc((var(--baseline) / 5)) calc((var(--baseline) / 2)); + line-height: var(--base-line-height); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification.success { + border-top: 3px solid var(--success); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification.success .icon { + margin-right: 15px; + color: var(--success); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification.error { + border-top: 3px solid var(--danger); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification.error .icon { + margin-right: 15px; + color: var(--danger); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification.warning { + border-top: 3px solid var(--warning); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification.warning .icon { + margin-right: 15px; + color: var(--warning); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification.general { + border-top: 3px solid var(--general-color-accent); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification.general .icon { + margin-right: 15px; + color: var(--general-color-accent); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification.problem-hint { + border: 1px solid var(--uxpl-gray-background); + border-radius: 6px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification.problem-hint .icon { + margin-right: calc(3 * var(--baseline) / 4); + color: var(--uxpl-gray-dark); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification.problem-hint li { + color: var(--uxpl-gray-base); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification.problem-hint li strong { + color: var(--uxpl-gray-dark); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification .icon { + float: left; + position: relative; + top: calc(var(--baseline) / 5); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification .notification-message { + display: inline-block; + width: 69.38776%; + margin-bottom: 8px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification .notification-message ol { + list-style: none outside none; + padding: 0; + margin: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification .notification-message ol li:not(:last-child) { + margin-bottom: calc(var(--baseline) / 4); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification .notification-btn-wrapper { + float: right; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification-btn { + float: right; + padding: calc((var(--baseline) / 10)) calc((var(--baseline) / 4)); + min-width: calc((var(--baseline) * 3)); + display: block; + clear: both; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .notification-btn:first-child { + margin-bottom: calc(var(--baseline) / 4); +} + +.xmodule_display.xmodule_ProblemBlock div.problem button:hover { + background-image: none; + box-shadow: none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem button:focus { + box-shadow: none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem button.btn-default { + background-color: transparent; +} + +.xmodule_display.xmodule_ProblemBlock div.problem button.btn-brand:hover { + background-color: var(--btn-brand-focus-background); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .review-btn { + color: var(--blue); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .review-btn.sr { + color: var(--blue); +} + +.xmodule_display.xmodule_ProblemBlock div.problem div.capa_reset { + padding: 25px; + background-color: var(--error-color-light); + border: 1px solid var(--error-color); + border-radius: 3px; + font-size: 1em; + margin-top: calc(var(--baseline) / 2); + margin-bottom: calc(var(--baseline) / 2); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .capa_reset>h2 { + color: #a00; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .capa_reset li { + font-size: 0.9em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .hints { + border: 1px solid var(--gray-l3); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .hints h3 { + padding: 9px; + border-bottom: 1px solid #e3e3e3; + background: #eee; + text-shadow: 0 1px 0 var(--white); + font-size: 1em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .hints div { + border-bottom: 1px solid #ddd; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .hints div:last-child { + border-bottom: none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .hints div p { + margin-bottom: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .hints div header a { + display: block; + padding: 9px; + background: var(--gray-l6); + box-shadow: inset 0 0 0 1px var(--white); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .hints div>section { + padding: 9px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .test { + padding-top: 18px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .test header { + margin-bottom: 12px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .test header h3 { + color: #aaa; + font-style: normal; + font-size: 0.9em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .test>section { + position: relative; + margin-bottom: calc((var(--baseline) / 2)); + padding: 9px 9px var(--baseline); + border: 1px solid #ddd; + border-radius: 3px; + background: var(--white); + box-shadow: inset 0 0 0 1px #eee; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .test>section p:last-of-type { + margin-bottom: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .test>section .shortform { + margin-bottom: .6em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .test>section a.full { + position: absolute; + top: 0; + right: 0; + bottom: 1px; + left: 0; + box-sizing: border-box; + display: block; + padding: calc((var(--baseline) / 5)); + background: var(--gray-l4); + text-align: right; + font-size: 1em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .test>section a.full.full-top { + position: absolute; + top: 1px; + right: 0; + bottom: auto; + left: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .test>section a.full.full-bottom { + position: absolute; + top: auto; + right: 0; + bottom: 1px; + left: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section { + padding-top: calc((var(--baseline) * 1.5)); + padding-left: var(--baseline); + background-color: #fafafa; + color: #2c2c2c; + font-size: 1em; + font-family: monospace; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section header { + font-size: 1.4em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform { + margin: 0; + padding: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-errors { + margin: calc((var(--baseline) / 4)); + padding: calc((var(--baseline) / 2)) calc((var(--baseline) / 2)) calc((var(--baseline) / 2)) calc((var(--baseline) * 2)); + background: url("var(--static-path)/images/incorrect-icon.png") center left no-repeat; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-errors li { + color: #b00; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-output { + margin: calc(var(--baseline) / 4); + padding: var(--baseline) 0 calc((var(--baseline) * 0.75)) 50px; + border-top: 1px solid #ddd; + border-left: var(--baseline) solid #fafafa; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-output h4 { + font-size: 1em; + font-family: monospace; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-output dl { + margin: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-output dt { + margin-top: var(--baseline); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-output dd { + margin-left: 24pt; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-correct { + background: url("var(--static-path)/images/correct-icon.png") left 20px no-repeat; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-correct .result-actual-output { + color: #090; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-partially-correct { + background: url("var(--static-path)/images/partially-correct-icon.png") left 20px no-repeat; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-partially-correct .result-actual-output { + color: #090; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-incorrect { + background: url("var(--static-path)/images/incorrect-icon.png") left 20px no-repeat; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .result-incorrect .result-actual-output { + color: #b00; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .markup-text { + margin: calc((var(--baseline) / 4)); + padding: var(--baseline) 0 15px 50px; + border-top: 1px solid #ddd; + border-left: 20px solid #fafafa; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .markup-text bs { + color: #b00; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .external-grader-message section .longform .markup-text bg { + color: #bda046; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .rubric tr { + margin: calc((var(--baseline) / 2)) 0; + height: 100%; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .rubric td { + margin: calc((var(--baseline) / 2)) 0; + padding: var(--baseline) 0; + height: 100%; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .rubric th { + margin: calc((var(--baseline) / 4)); + padding: calc((var(--baseline) / 4)); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .rubric label, +.xmodule_display.xmodule_ProblemBlock div.problem .rubric .view-only { + position: relative; + display: inline-block; + margin: 3px; + padding: calc((var(--baseline) * 0.75)); + min-width: 50px; + min-height: 50px; + width: 150px; + height: 100%; + background-color: var(--gray-l3); + font-size: .9em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .rubric .grade { + position: absolute; + right: 0; + bottom: 0; + margin: calc((var(--baseline) / 2)); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .rubric .selected-grade { + background: #666; + color: white; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .rubric input[type=radio]:checked+label { + background: #666; + color: white; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .rubric input[class='score-selection'] { + display: none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input { + margin: 0 0 1em 0; + border: 1px solid var(--gray-l3); + border-radius: 1em; + /* for debugging the input value field. enable the debug flag on the inputtype */ +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .annotation-header { + padding: .5em 1em; + border-bottom: 1px solid var(--gray-l3); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .annotation-body { + padding: .5em 1em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input a.annotation-return { + float: right; + font: inherit; + font-weight: normal; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input a.annotation-return::after { + content: " \2191"; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .block, +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input ul.tags { + margin: .5em 0; + padding: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .block-highlight { + padding: .5em; + border: 1px solid rgba(214, 214, 0, 0.3); + background-color: rgba(255, 255, 10, 0.3); + color: #333; + font-style: normal; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .block-comment { + font-style: italic; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input ul.tags { + display: block; + margin-left: 1em; + list-style-type: none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input ul.tags li { + position: relative; + display: block; + margin: 1em 0 0 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input ul.tags li .tag { + display: inline-block; + margin-left: calc((var(--baseline) * 2)); + border: 1px solid #666666; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input ul.tags li .tag.selected { + background-color: rgba(255, 255, 10, 0.3); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input ul.tags li .tag-status { + position: absolute; + left: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input ul.tags li .tag-status, +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input ul.tags li .tag { + padding: .25em .5em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input textarea.comment { + padding: 0.2em 0.4em; + width: 100%; + height: 7.2em; + line-height: 1.4em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .answer-annotation { + display: block; + margin: 0; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .debug-value { + margin: 1em 0; + padding: 1em; + border: 1px solid var(--black); + background-color: #999; + color: var(--white); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .debug-value input[type="text"] { + width: 100%; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .debug-value pre { + background-color: var(--gray-l3); + color: var(--black); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .debug-value::before { + display: block; + content: "debug input value"; + font-size: 1.5em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup input[type="text"] { + margin-bottom: 0.5em; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup label.choicetextgroup_correct input[type="text"], +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup section.choicetextgroup_correct input[type="text"] { + border-color: var(--correct); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup label.choicetextgroup_partially-correct input[type="text"], +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup section.choicetextgroup_partially-correct input[type="text"] { + border-color: var(--partially-correct); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup label.choicetextgroup_show_correct::after, +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup section.choicetextgroup_show_correct::after { + margin-left: calc((var(--baseline) * 0.75)); + content: url("var(--static-path)/images/correct-icon.png"); +} + +.xmodule_display.xmodule_ProblemBlock div.problem .choicetextgroup span.mock_label { + cursor: default; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .status { + display: inline-block; + position: relative; + top: 3px; + width: 25px; + height: 20px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .status.unsubmitted .status-icon, +.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .status.unanswered .status-icon { + content: ''; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .status.unsubmitted .status-message, +.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .status.unanswered .status-message { + display: none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .correct .status-icon::after { + color: var(--correct); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .incorrect .status-icon::after { + color: var(--incorrect); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .partially-correct .status-icon::after { + color: var(--partially-correct); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .imageinput.capa_inputtype .submitted { + content: ''; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .tag-status { + display: inline-block; + position: relative; + top: 3px; + width: 25px; + height: 20px; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .tag-status.unsubmitted .status-icon, +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .tag-status.unanswered .status-icon { + content: ''; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .tag-status.unsubmitted .status-message, +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .tag-status.unanswered .status-message { + display: none; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .correct .status-icon::after { + color: var(--correct); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .incorrect .status-icon::after { + color: var(--incorrect); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .partially-correct .status-icon::after { + color: var(--partially-correct); + font-size: 1.2em; + content: ""; +} + +.xmodule_display.xmodule_ProblemBlock div.problem .annotation-input .submitted { + content: ''; +} + +.xmodule_display.xmodule_ProblemBlock .problems-wrapper .loading-spinner { + text-align: center; + color: var(--gray-d1); +} diff --git a/xmodule/static/css-builtin-blocks/ProblemBlockEditor.css b/xmodule/static/css-builtin-blocks/ProblemBlockEditor.css new file mode 100644 index 000000000000..467717a4076e --- /dev/null +++ b/xmodule/static/css-builtin-blocks/ProblemBlockEditor.css @@ -0,0 +1,221 @@ +@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700"); + +.xmodule_edit.xmodule_ProblemBlock .ui-col-wide { + width: 74.46809%; + margin-right: 2.12766%; + float: left; +} + +.xmodule_edit.xmodule_ProblemBlock .ui-col-narrow { + width: 23.40426%; + float: left; +} + +.xmodule_edit.xmodule_ProblemBlock .ui-loading { + box-shadow: inset 0 1px 2px 1px rgba(0, 0, 0, 0.2); + padding: 15px 20px; +} + +.xmodule_edit.xmodule_ProblemBlock .ui-loading { + animation: fadeIn 0.25s linear 1; + opacity: 0.6; + background-color: #fff; + padding: 30px 20px; + text-align: center; +} + +.xmodule_edit.xmodule_ProblemBlock .ui-loading .spin { + display: inline-block; +} + +.xmodule_edit.xmodule_ProblemBlock .ui-loading .copy { + padding-left: 5px; +} + +.xmodule_edit.xmodule_ProblemBlock .is-hidden { + display: none; +} + +.xmodule_edit.xmodule_ProblemBlock .editor { + position: relative; +} + +.xmodule_edit.xmodule_ProblemBlock .editor .row { + position: relative; +} + +.xmodule_edit.xmodule_ProblemBlock .editor .editor-bar { + background-color: #d4dee8; + background-image: -webkit-linear-gradient(top, #d4dee8, #c9d5e2); + background-image: linear-gradient(to bottom, #d4dee8, #c9d5e2); + position: relative; + padding: calc(var(--baseline) / 4); + border-bottom-color: #a5aaaf; +} + +.xmodule_edit.xmodule_ProblemBlock .editor .editor-bar:after { + content: ""; + display: table; + clear: both; +} + +.xmodule_edit.xmodule_ProblemBlock .editor .editor-bar button { + display: inline-block; + float: left; + padding: 3px calc(var(--baseline) / 2) 5px; + margin-left: 7px; + border: 0; + border-radius: 2px; + background: transparent; +} + +.xmodule_edit.xmodule_ProblemBlock .editor .editor-bar button .icon { + height: 21px; +} + +.xmodule_edit.xmodule_ProblemBlock .editor .editor-bar button:hover, +.xmodule_edit.xmodule_ProblemBlock .editor .editor-bar button:focus { + background: rgba(255, 255, 255, 0.5); +} + +.xmodule_edit.xmodule_ProblemBlock .editor .editor-tabs { + position: absolute; + top: 10px; + right: 10px; + text-align: left; + direction: ltr; +} + +.xmodule_edit.xmodule_ProblemBlock .editor .editor-tabs li { + float: left; + margin-right: calc(var(--baseline) / 4); +} + +.xmodule_edit.xmodule_ProblemBlock .editor .editor-tabs li:last-child { + margin-right: 0; +} + +.xmodule_edit.xmodule_ProblemBlock .editor .editor-tabs .tab { + display: block; + height: 24px; + padding: 7px 20px 3px; + border: 1px solid #a5aaaf; + border-radius: 3px 3px 0 0; + background-color: var(--transparent); + background-image: -webkit-linear-gradient(top, var(--transparent) 87%, rgba(0, 0, 0, 0.06)); + background-image: linear-gradient(to bottom, var(--transparent) 87%, rgba(0, 0, 0, 0.06)); + background-color: #e5ecf3; + font-size: 13px; + color: #3c3c3c; + box-shadow: 1px -1px 1px rgba(0, 0, 0, 0.05); +} + +.xmodule_edit.xmodule_ProblemBlock .editor .editor-tabs .tab.current { + background: var(--white); + border-bottom-color: var(--white); +} + +.xmodule_edit.xmodule_ProblemBlock .editor-bar .editor-tabs .advanced-toggle { + height: auto; + margin-top: -4px; + padding: 3px 9px; + font-size: 12px; + color: var(--link-color); +} + +.xmodule_edit.xmodule_ProblemBlock .editor-bar .editor-tabs .advanced-toggle.current { + border: 1px solid var(--lightGrey) !important; + border-radius: 3px !important; + background: var(--lightGrey) !important; + color: var(--darkGrey) !important; + pointer-events: none; + cursor: none; +} + +.xmodule_edit.xmodule_ProblemBlock .editor-bar .editor-tabs .advanced-toggle.current:hover, +.xmodule_edit.xmodule_ProblemBlock .editor-bar .editor-tabs .advanced-toggle.current:focus { + box-shadow: 0 0 0 0 !important; + background-color: var(--white); +} + +.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet { + position: absolute; + top: 41px; + left: 70%; + width: 0; + border-left: 1px solid var(--gray-l2); + background-color: var(--lightGrey); + overflow: hidden; +} + +.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet.shown { + width: 30%; + height: 92%; + overflow-y: scroll; +} + +.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet .cheatsheet-wrapper { + padding: 5%; +} + +.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet h6 { + margin-top: 4px; + margin-bottom: 7px; + margin-left: 4px; + font-size: 15px; + font-weight: 700; + display: inline-block; + vertical-align: top; +} + +.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet .row { + padding-bottom: 5px !important; + margin-bottom: 10px !important; + border-bottom: 1px solid #ddd !important; +} + +.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet .row:after { + content: ""; + display: table; + clear: both; +} + +.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet .row:last-child { + border-bottom: none !important; + margin-bottom: 0 !important; +} + +.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet .col { + display: block; +} + +.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet .col.sample { + margin-right: 30px; +} + +.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet .col.sample .icon { + height: calc(var(--baseline) * 1.5); +} + +.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet pre { + font-size: 12px; + line-height: 18px; +} + +.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet code { + padding: 0; + background: none; +} + +.xmodule_edit.xmodule_ProblemBlock .problem-editor .markdown-box+.CodeMirror { + padding: 10px; + width: 69%; +} + +.xmodule_edit.xmodule_ProblemBlock .problem-editor-icon { + display: inline-block; + width: 26px; + height: 21px; + vertical-align: middle; + color: var(--body-color); +} diff --git a/xmodule/static/css-builtin-blocks/SequenceBlockDisplay.css b/xmodule/static/css-builtin-blocks/SequenceBlockDisplay.css new file mode 100644 index 000000000000..24d9b8ae6c32 --- /dev/null +++ b/xmodule/static/css-builtin-blocks/SequenceBlockDisplay.css @@ -0,0 +1,359 @@ +@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700"); + +.xmodule_display.xmodule_SequenceBlock .block-link { + border-left: 1px solid var(--border-color); + display: block; +} + +.xmodule_display.xmodule_SequenceBlock .block-link:hover, +.xmodule_display.xmodule_SequenceBlock .block-link:focus { + background: none; +} + +.xmodule_display.xmodule_SequenceBlock .topbar, +.xmodule_display.xmodule_SequenceBlock .sequence-nav { + border-bottom: 1px solid var(--border-color); +} + +.xmodule_display.xmodule_SequenceBlock .topbar:after, +.xmodule_display.xmodule_SequenceBlock .sequence-nav:after { + content: ""; + display: table; + clear: both; +} + +@media print { + + .xmodule_display.xmodule_SequenceBlock .topbar, + .xmodule_display.xmodule_SequenceBlock .sequence-nav { + display: none; + } +} + +.xmodule_display.xmodule_SequenceBlock .topbar a.block-link, +.xmodule_display.xmodule_SequenceBlock .sequence-nav a.block-link { + border-left: 1px solid var(--border-color); + display: block; +} + +.xmodule_display.xmodule_SequenceBlock .topbar a.block-link:hover, +.xmodule_display.xmodule_SequenceBlock .sequence-nav a.block-link:hover, +.xmodule_display.xmodule_SequenceBlock .topbar a.block-link:focus, +.xmodule_display.xmodule_SequenceBlock .sequence-nav a.block-link:focus { + background: none; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button { + background-color: transparent; + background-image: none; + background-position: center 14px; + background-repeat: no-repeat; + border: none; + border-radius: 0; + background-clip: border-box; + box-shadow: none; + box-sizing: content-box; + font-family: inherit; + font-size: inherit; + font-weight: inherit; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav { + margin: 0 auto var(--baseline); + position: relative; + border-bottom: none; + z-index: 0; + height: 50px; + display: flex; + justify-content: center; +} + +@media print { + .xmodule_display.xmodule_SequenceBlock .sequence-nav { + display: none; + } +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav .sequence-list-wrapper { + position: relative; + height: 100%; + flex-grow: 1; +} + +@media (max-width: 575.98px) { + .xmodule_display.xmodule_SequenceBlock .sequence-nav .sequence-list-wrapper { + white-space: nowrap; + overflow-x: scroll; + } +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol { + display: flex; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li { + box-sizing: border-box; + min-width: 40px; + flex-grow: 1; + border-color: var(--border-color); + border-width: 1px; + border-top-style: solid; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li:not(:last-child) { + border-right-style: solid; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button { + width: 100%; + height: 49px; + position: relative; + margin: 0; + padding: 0; + display: block; + text-align: center; + border-color: var(--border-color); + border-width: 1px; + border-bottom-style: solid; + box-sizing: border-box; + overflow: visible; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button .icon { + display: inline-block; + line-height: 100%; + font-size: 110%; + color: #5a5a5a; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button .fa-bookmark { + color: var(--link-color); +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button.seq_video .icon::before { + content: "\f008"; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button.seq_other .icon::before { + content: "\f02d"; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button.seq_vertical .icon::before { + content: "\f00b"; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button.seq_problem .icon::before { + content: "\f044"; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button .sequence-tooltip { + text-align: left; + margin-top: 12px; + background: #333333; + color: var(--white); + font-family: var(--font-family-sans-serif); + line-height: lh(); + right: 0; + padding: 6px; + position: absolute; + top: 48px; + text-shadow: 0 -1px 0 var(--black); + white-space: pre; + pointer-events: none; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button .sequence-tooltip:empty { + background: none; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button .sequence-tooltip:empty::after { + display: none; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button .sequence-tooltip::after { + transform: rotate(45deg); + right: 18px; + background: #333333; + content: " "; + display: block; + height: 10px; + right: 18px; + position: absolute; + top: -5px; + width: 10px; +} + +body.touch-based-device .xmodule_display.xmodule_SequenceBlock .sequence-nav ol li button:hover p { + display: none; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav-button { + display: block; + top: 0; + min-width: 40px; + max-width: 40px; + height: 100%; + text-shadow: none; + background: none; + background-color: #fff; + border-color: var(--border-color); + box-shadow: none; + font-size: inherit; + font-weight: normal; + padding: 0; + white-space: nowrap; + overflow-x: hidden; +} + +@media (min-width: 768px) { + .xmodule_display.xmodule_SequenceBlock .sequence-nav-button { + min-width: 120px; + max-width: 200px; + text-overflow: ellipsis; + } + + .xmodule_display.xmodule_SequenceBlock .sequence-nav-button span:not(:last-child) { + padding-right: calc((var(--baseline) / 2)); + } +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav-button .sequence-nav-button-label { + display: none; +} + +@media (min-width: 768px) { + .xmodule_display.xmodule_SequenceBlock .sequence-nav-button .sequence-nav-button-label { + display: inline; + } +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav-button.button-previous { + order: -999; +} + +@media (min-width: 768px) { + .xmodule_display.xmodule_SequenceBlock .sequence-nav-button.button-previous { + left: 0; + -webkit-border-top-left-radius: 3px; + -moz-border-topleft-radius: 3px; + border-top-left-radius: 3px; + -webkit-border-top-right-radius: 0; + -moz-border-topright-radius: 0; + border-top-right-radius: 0; + -webkit-border-bottom-right-radius: 0; + -moz-border-bottomright-radius: 0; + border-bottom-right-radius: 0; + -webkit-border-bottom-left-radius: 3px; + -moz-border-bottomleft-radius: 3px; + border-bottom-left-radius: 3px; + } +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav-button.button-next { + order: 999; +} + +@media (min-width: 768px) { + .xmodule_display.xmodule_SequenceBlock .sequence-nav-button.button-next { + right: 0; + -webkit-border-top-left-radius: 0; + -moz-border-topleft-radius: 0; + border-top-left-radius: 0; + -webkit-border-top-right-radius: 3px; + -moz-border-topright-radius: 3px; + border-top-right-radius: 3px; + -webkit-border-bottom-right-radius: 3px; + -moz-border-bottomright-radius: 3px; + border-bottom-right-radius: 3px; + -webkit-border-bottom-left-radius: 0; + -moz-border-bottomleft-radius: 0; + border-bottom-left-radius: 0; + } +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav-button.disabled { + cursor: normal; +} + +.xmodule_display.xmodule_SequenceBlock .seq_contents { + display: none; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-bottom { + position: relative; + height: 45px; + margin: lh(2) auto; + display: flex; + justify-content: center; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-bottom .sequence-nav-button { + position: relative; + min-width: 120px; + max-width: 200px; + text-overflow: ellipsis; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-bottom .sequence-nav-button:last-of-type { + border-left: none; +} + +@media print { + .xmodule_display.xmodule_SequenceBlock .sequence-bottom { + display: none; + } +} + +.xmodule_display.xmodule_SequenceBlock #seq_content:focus, +.xmodule_display.xmodule_SequenceBlock #seq_content:active { + outline: none; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav-button.focused, +.xmodule_display.xmodule_SequenceBlock .sequence-nav-button:hover, +.xmodule_display.xmodule_SequenceBlock .sequence-nav-button:active, +.xmodule_display.xmodule_SequenceBlock .sequence-nav-button.active, +.xmodule_display.xmodule_SequenceBlock .sequence-nav button.focused, +.xmodule_display.xmodule_SequenceBlock .sequence-nav button:hover, +.xmodule_display.xmodule_SequenceBlock .sequence-nav button:active, +.xmodule_display.xmodule_SequenceBlock .sequence-nav button.active { + padding-top: 2px; + background-color: #0075b4; +} + +.xmodule_display.xmodule_SequenceBlock .sequence-nav-button.focused .icon, +.xmodule_display.xmodule_SequenceBlock .sequence-nav-button:hover .icon, +.xmodule_display.xmodule_SequenceBlock .sequence-nav-button:active .icon, +.xmodule_display.xmodule_SequenceBlock .sequence-nav-button.active .icon, +.xmodule_display.xmodule_SequenceBlock .sequence-nav button.focused .icon, +.xmodule_display.xmodule_SequenceBlock .sequence-nav button:hover .icon, +.xmodule_display.xmodule_SequenceBlock .sequence-nav button:active .icon, +.xmodule_display.xmodule_SequenceBlock .sequence-nav button.active .icon { + color: #fff; +} + +@media (min-width: 576px) { + + .xmodule_display.xmodule_SequenceBlock .sequence-nav-button.focused, + .xmodule_display.xmodule_SequenceBlock .sequence-nav-button:hover, + .xmodule_display.xmodule_SequenceBlock .sequence-nav-button:active, + .xmodule_display.xmodule_SequenceBlock .sequence-nav-button.active, + .xmodule_display.xmodule_SequenceBlock .sequence-nav button.focused, + .xmodule_display.xmodule_SequenceBlock .sequence-nav button:hover, + .xmodule_display.xmodule_SequenceBlock .sequence-nav button:active, + .xmodule_display.xmodule_SequenceBlock .sequence-nav button.active { + border-bottom: 3px solid var(--link-color); + background-color: #fff; + } + + .xmodule_display.xmodule_SequenceBlock .sequence-nav-button.focused .icon, + .xmodule_display.xmodule_SequenceBlock .sequence-nav-button:hover .icon, + .xmodule_display.xmodule_SequenceBlock .sequence-nav-button:active .icon, + .xmodule_display.xmodule_SequenceBlock .sequence-nav-button.active .icon, + .xmodule_display.xmodule_SequenceBlock .sequence-nav button.focused .icon, + .xmodule_display.xmodule_SequenceBlock .sequence-nav button:hover .icon, + .xmodule_display.xmodule_SequenceBlock .sequence-nav button:active .icon, + .xmodule_display.xmodule_SequenceBlock .sequence-nav button.active .icon { + color: #0a0a0a; + } +} diff --git a/xmodule/static/css-builtin-blocks/VideoBlockDisplay.css b/xmodule/static/css-builtin-blocks/VideoBlockDisplay.css index 93c4b6adcec3..29e9fce6ef58 100644 --- a/xmodule/static/css-builtin-blocks/VideoBlockDisplay.css +++ b/xmodule/static/css-builtin-blocks/VideoBlockDisplay.css @@ -1,19 +1,11 @@ @import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700"); -.xmodule_display.xmodule_VideoBlock { - /* stylelint-disable-line */ - /* stylelint-disable-line */ - /* stylelint-disable-line */ - /* stylelint-disable-line */ - /* stylelint-disable-line */ - /* stylelint-disable-line */ -} - .xmodule_display.xmodule_VideoBlock { margin-bottom: calc((var(--baseline) * 1.5)); } -.xmodule_display.xmodule_VideoBlock .is-hidden, .xmodule_display.xmodule_VideoBlock .video.closed .subtitles { +.xmodule_display.xmodule_VideoBlock .is-hidden, +.xmodule_display.xmodule_VideoBlock .video.closed .subtitles { display: none; } @@ -32,7 +24,9 @@ clear: both; } -.xmodule_display.xmodule_VideoBlock .video:focus, .xmodule_display.xmodule_VideoBlock .video:active, .xmodule_display.xmodule_VideoBlock .video:hover { +.xmodule_display.xmodule_VideoBlock .video:focus, +.xmodule_display.xmodule_VideoBlock .video:active, +.xmodule_display.xmodule_VideoBlock .video:hover { border: 0; } @@ -116,7 +110,8 @@ margin: 0; } -.xmodule_display.xmodule_VideoBlock .video .wrapper-downloads .wrapper-download-transcripts .list-download-transcripts .transcript-option a.btn, .xmodule_display.xmodule_VideoBlock .video .wrapper-downloads .wrapper-download-transcripts .list-download-transcripts .transcript-option a.btn-link { +.xmodule_display.xmodule_VideoBlock .video .wrapper-downloads .wrapper-download-transcripts .list-download-transcripts .transcript-option a.btn, +.xmodule_display.xmodule_VideoBlock .video .wrapper-downloads .wrapper-download-transcripts .list-download-transcripts .transcript-option a.btn-link { font-size: 16px !important; font-weight: unset; } @@ -266,12 +261,14 @@ opacity: 0.5; } -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .closed-captions.is-visible:hover, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .closed-captions.is-visible.is-dragging { +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .closed-captions.is-visible:hover, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .closed-captions.is-visible.is-dragging { background: black; cursor: move; } -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .closed-captions.is-visible:hover::before, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .closed-captions.is-visible.is-dragging::before { +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .closed-captions.is-visible:hover::before, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .closed-captions.is-visible.is-dragging::before { opacity: 1; } @@ -280,11 +277,11 @@ min-height: 158px; } -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-player > div { +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-player>div { height: 100%; } -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-player > div.hidden { +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-player>div.hidden { display: none; } @@ -327,7 +324,8 @@ } .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls:hover ul, -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls:hover div, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls:focus ul, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls:hover div, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls:focus ul, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls:focus div { opacity: 1; } @@ -345,11 +343,14 @@ color: #cfd8dc; } -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .control:hover, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .control:focus { +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .control:hover, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .control:focus { background: #171a1b; } -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .control:active, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .is-active.control, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .active.control { +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .control:active, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .is-active.control, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .active.control { color: #0ea6ec; } @@ -411,7 +412,8 @@ box-shadow: none; } -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .slider .ui-slider-handle:focus, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .slider .ui-slider-handle:hover { +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .slider .ui-slider-handle:focus, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .slider .ui-slider-handle:hover { background-color: #db8baf; border-color: #db8baf; } @@ -465,7 +467,7 @@ } .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .speed-button:focus, -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .volume > .control:focus, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .volume>.control:focus, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .add-fullscreen:focus, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .auto-advance:focus, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .quality-control:focus, @@ -513,7 +515,8 @@ white-space: nowrap; } -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .menu-container .menu li .speed-option:hover, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .menu-container .menu li .speed-option:focus, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .menu-container .menu li .speed-option:hover, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .menu-container .menu li .speed-option:focus, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .menu-container .menu li .control-lang:hover, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .menu-container .menu li .control-lang:focus { background-color: #4f595d; @@ -585,7 +588,7 @@ opacity: 1; } -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .volume:not(:first-child) > a { +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .volume:not(:first-child)>a { border-left: none; } @@ -622,7 +625,8 @@ box-shadow: none; } -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .volume .volume-slider-container .volume-slider .ui-slider-handle:hover, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .volume .volume-slider-container .volume-slider .ui-slider-handle:focus { +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .volume .volume-slider-container .volume-slider .ui-slider-handle:hover, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .volume .volume-slider-container .volume-slider .ui-slider-handle:focus { background: #db8baf; border-color: #db8baf; } @@ -643,7 +647,8 @@ color: #0ea6ec; } -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .quality-control.is-hidden, .xmodule_display.xmodule_VideoBlock .video.closed .video-wrapper .video-controls .secondary-controls .quality-control.subtitles { +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .quality-control.is-hidden, +.xmodule_display.xmodule_VideoBlock .video.closed .video-wrapper .video-controls .secondary-controls .quality-control.subtitles { display: none !important; } @@ -651,7 +656,7 @@ color: #0ea6ec; } -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .lang > .hide-subtitles { +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .lang>.hide-subtitles { transition: none; } @@ -715,7 +720,8 @@ outline-offset: -1px; } -.xmodule_display.xmodule_VideoBlock .video .subtitles .subtitles-menu li:hover, .xmodule_display.xmodule_VideoBlock .video .subtitles .subtitles-menu li:focus { +.xmodule_display.xmodule_VideoBlock .video .subtitles .subtitles-menu li:hover, +.xmodule_display.xmodule_VideoBlock .video .subtitles .subtitles-menu li:focus { text-decoration: underline; } @@ -890,11 +896,16 @@ width: calc((var(--baseline) * 4)); } -.xmodule_display.xmodule_VideoBlock .video .video-pre-roll .btn-play.btn-pre-roll:hover, .xmodule_display.xmodule_VideoBlock .video .video-pre-roll .btn-play.btn-pre-roll:focus { +.xmodule_display.xmodule_VideoBlock .video .video-pre-roll .btn-play.btn-pre-roll:hover, +.xmodule_display.xmodule_VideoBlock .video .video-pre-roll .btn-play.btn-pre-roll:focus { background: var(--blue); } -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .slider .ui-slider-handle, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .menu-container .menu li, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .volume .volume-slider-container .volume-slider .ui-slider-handle, .xmodule_display.xmodule_VideoBlock .video .subtitles .subtitles-menu li, .xmodule_display.xmodule_VideoBlock .a11y-menu-container .a11y-menu-list li { +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .slider .ui-slider-handle, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .menu-container .menu li, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .volume .volume-slider-container .volume-slider .ui-slider-handle, +.xmodule_display.xmodule_VideoBlock .video .subtitles .subtitles-menu li, +.xmodule_display.xmodule_VideoBlock .a11y-menu-container .a11y-menu-list li { cursor: pointer; } @@ -902,15 +913,19 @@ z-index: 0; } -.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .menu-container .menu, .xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .volume .volume-slider-container { +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .menu-container .menu, +.xmodule_display.xmodule_VideoBlock .video .video-wrapper .video-controls .secondary-controls .volume .volume-slider-container { z-index: 10; } -.xmodule_display.xmodule_VideoBlock .video .video-pre-roll, .xmodule_display.xmodule_VideoBlock .a11y-menu-container .a11y-menu-list { +.xmodule_display.xmodule_VideoBlock .video .video-pre-roll, +.xmodule_display.xmodule_VideoBlock .a11y-menu-container .a11y-menu-list { z-index: 1000; } -.xmodule_display.xmodule_VideoBlock .video.video-fullscreen, .xmodule_display.xmodule_VideoBlock .video.video-fullscreen .tc-wrapper .video-controls, .xmodule_display.xmodule_VideoBlock .overlay { +.xmodule_display.xmodule_VideoBlock .video.video-fullscreen, +.xmodule_display.xmodule_VideoBlock .video.video-fullscreen .tc-wrapper .video-controls, +.xmodule_display.xmodule_VideoBlock .overlay { z-index: 10000; } @@ -919,7 +934,7 @@ z-index: 100000; } -.xmodule_display.xmodule_VideoBlock .video-tracks .a11y-menu-container > a::after { +.xmodule_display.xmodule_VideoBlock .video-tracks .a11y-menu-container>a::after { font-family: FontAwesome; -webkit-font-smoothing: antialiased; display: inline-block; @@ -962,7 +977,8 @@ line-height: 23px; } -.xmodule_display.xmodule_VideoBlock .a11y-menu-container .a11y-menu-list li a:hover, .xmodule_display.xmodule_VideoBlock .a11y-menu-container .a11y-menu-list li a:focus { +.xmodule_display.xmodule_VideoBlock .a11y-menu-container .a11y-menu-list li a:hover, +.xmodule_display.xmodule_VideoBlock .a11y-menu-container .a11y-menu-list li a:focus { color: var(--gray-d1); } @@ -982,16 +998,16 @@ border-left: 1px solid #eee; } -.xmodule_display.xmodule_VideoBlock .video-tracks .a11y-menu-container.open > a { +.xmodule_display.xmodule_VideoBlock .video-tracks .a11y-menu-container.open>a { background-color: var(--action-primary-active-bg); color: var(--very-light-text); } -.xmodule_display.xmodule_VideoBlock .video-tracks .a11y-menu-container.open > a::after { +.xmodule_display.xmodule_VideoBlock .video-tracks .a11y-menu-container.open>a::after { color: var(--very-light-text); } -.xmodule_display.xmodule_VideoBlock .video-tracks .a11y-menu-container > a { +.xmodule_display.xmodule_VideoBlock .video-tracks .a11y-menu-container>a { transition: all var(--tmg-f2) ease-in-out 0s; font-size: 12px; display: block; @@ -1006,7 +1022,7 @@ text-overflow: ellipsis; } -.xmodule_display.xmodule_VideoBlock .video-tracks .a11y-menu-container > a::after { +.xmodule_display.xmodule_VideoBlock .video-tracks .a11y-menu-container>a::after { content: "\f0d7"; position: absolute; right: calc((var(--baseline) * 0.5)); @@ -1061,10 +1077,10 @@ outline: none; } -.xmodule_display.xmodule_VideoBlock .contextmenu .menu-item > span, -.xmodule_display.xmodule_VideoBlock .contextmenu .submenu-item > span, -.xmodule_display.xmodule_VideoBlock .submenu .menu-item > span, -.xmodule_display.xmodule_VideoBlock .submenu .submenu-item > span { +.xmodule_display.xmodule_VideoBlock .contextmenu .menu-item>span, +.xmodule_display.xmodule_VideoBlock .contextmenu .submenu-item>span, +.xmodule_display.xmodule_VideoBlock .submenu .menu-item>span, +.xmodule_display.xmodule_VideoBlock .submenu .submenu-item>span { color: #333; } @@ -1083,10 +1099,10 @@ color: var(--white); } -.xmodule_display.xmodule_VideoBlock .contextmenu .menu-item:focus > span, -.xmodule_display.xmodule_VideoBlock .contextmenu .submenu-item:focus > span, -.xmodule_display.xmodule_VideoBlock .submenu .menu-item:focus > span, -.xmodule_display.xmodule_VideoBlock .submenu .submenu-item:focus > span { +.xmodule_display.xmodule_VideoBlock .contextmenu .menu-item:focus>span, +.xmodule_display.xmodule_VideoBlock .contextmenu .submenu-item:focus>span, +.xmodule_display.xmodule_VideoBlock .submenu .menu-item:focus>span, +.xmodule_display.xmodule_VideoBlock .submenu .submenu-item:focus>span { color: var(--white); } @@ -1116,13 +1132,13 @@ color: var(--white); } -.xmodule_display.xmodule_VideoBlock .contextmenu .submenu-item.is-opened > span, -.xmodule_display.xmodule_VideoBlock .submenu .submenu-item.is-opened > span { +.xmodule_display.xmodule_VideoBlock .contextmenu .submenu-item.is-opened>span, +.xmodule_display.xmodule_VideoBlock .submenu .submenu-item.is-opened>span { color: var(--white); } -.xmodule_display.xmodule_VideoBlock .contextmenu .submenu-item.is-opened > .submenu, -.xmodule_display.xmodule_VideoBlock .submenu .submenu-item.is-opened > .submenu { +.xmodule_display.xmodule_VideoBlock .contextmenu .submenu-item.is-opened>.submenu, +.xmodule_display.xmodule_VideoBlock .submenu .submenu-item.is-opened>.submenu { display: block; } diff --git a/xmodule/static/css-builtin-blocks/VideoBlockEditor.css b/xmodule/static/css-builtin-blocks/VideoBlockEditor.css index 4a570a5fec89..c509a017ba60 100644 --- a/xmodule/static/css-builtin-blocks/VideoBlockEditor.css +++ b/xmodule/static/css-builtin-blocks/VideoBlockEditor.css @@ -1,8 +1,5 @@ @import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700"); -.xmodule_edit.xmodule_VideoBlock { -} - .xmodule_edit.xmodule_VideoBlock .ui-col-wide { width: 74.46809%; margin-right: 2.12766%; @@ -125,7 +122,8 @@ cursor: default; } -.xmodule_edit.xmodule_VideoBlock .editor-with-tabs .edit-header .editor-tabs .inner_tab_wrap a.tab:hover, .xmodule_edit.xmodule_VideoBlock .editor-with-tabs .edit-header .editor-tabs .inner_tab_wrap a.tab:focus { +.xmodule_edit.xmodule_VideoBlock .editor-with-tabs .edit-header .editor-tabs .inner_tab_wrap a.tab:hover, +.xmodule_edit.xmodule_VideoBlock .editor-with-tabs .edit-header .editor-tabs .inner_tab_wrap a.tab:focus { box-shadow: inset 0 1px 2px 1px var(--shadow); background-image: linear-gradient(#009fe6, #009fe6) !important; } @@ -142,7 +140,7 @@ display: none; } -.xmodule_edit.xmodule_VideoBlock .editor-with-tabs .comp-subtitles-entry .comp-subtitles-import-list > li { +.xmodule_edit.xmodule_VideoBlock .editor-with-tabs .comp-subtitles-entry .comp-subtitles-import-list>li { display: block; margin: calc(var(--baseline) / 2) 0; } diff --git a/xmodule/static/css-builtin-blocks/WordCloudBlockDisplay.css b/xmodule/static/css-builtin-blocks/WordCloudBlockDisplay.css index 51050dcba9a1..8be5f77ede00 100644 --- a/xmodule/static/css-builtin-blocks/WordCloudBlockDisplay.css +++ b/xmodule/static/css-builtin-blocks/WordCloudBlockDisplay.css @@ -1,10 +1,5 @@ @import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700"); -.xmodule_display.xmodule_WordCloudBlock { - /* stylelint-disable-line */ - /* stylelint-disable-line */ -} - .xmodule_display.xmodule_WordCloudBlock .input-cloud { margin: calc((var(--baseline) / 4)); } diff --git a/xmodule/template_block.py b/xmodule/template_block.py index e6e69742cfbf..cdf83a556665 100644 --- a/xmodule/template_block.py +++ b/xmodule/template_block.py @@ -9,7 +9,7 @@ from web_fragments.fragment import Fragment from xmodule.editing_block import EditingMixin from xmodule.raw_block import RawMixin -from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_sass_to_fragment +from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_css_to_fragment from xmodule.x_module import ( ResourceTemplates, shim_xmodule_js, @@ -69,7 +69,7 @@ def studio_view(self, _context): fragment = Fragment( self.runtime.service(self, 'mako').render_cms_template(self.mako_template, self.get_context()) ) - add_sass_to_fragment(fragment, 'CustomTagBlockEditor.scss') + add_css_to_fragment(fragment, 'CustomTagBlockEditor.css') add_webpack_js_to_fragment(fragment, 'CustomTagBlockEditor') shim_xmodule_js(fragment, 'XMLEditingDescriptor') return fragment diff --git a/xmodule/tests/test_util_builtin_assets.py b/xmodule/tests/test_util_builtin_assets.py index ea5933c79215..b922ec4b6a65 100644 --- a/xmodule/tests/test_util_builtin_assets.py +++ b/xmodule/tests/test_util_builtin_assets.py @@ -10,64 +10,6 @@ from xmodule.util import builtin_assets -class AddSassToFragmentTests(TestCase): - """ - Tests for add_sass_to_fragment. - - We would have liked to also test two additional cases: - * When a theme is enabled, and add_sass_to_fragment is called with a - theme-overriden Sass file, then a URL to themed CSS is added. - * When a theme is enabled, but add_sass_to_fragment is called with a Sass - file that the theme doesn't override, then a URL to the original (unthemed) - CSS is added. - Unfortunately, under edx-platform tests, settings.STATICFILES_STORAGE does not - include the ThemeStorage class, so themed URL generation doesn't work. - """ - - def test_absolute_path_raises_value_error(self): - fragment = Fragment() - with self.assertRaises(ValueError): - builtin_assets.add_sass_to_fragment( - fragment, - "/openedx/edx-platform/xmodule/assets/ProblemBlockDisplay.scss", - ) - - def test_not_scss_raises_value_error(self): - fragment = Fragment() - with self.assertRaises(ValueError): - builtin_assets.add_sass_to_fragment( - fragment, - "vertical/public/js/vertical_student_view.js" - ) - - def test_misspelled_path_raises_not_found(self): - fragment = Fragment() - with self.assertRaises(FileNotFoundError): - builtin_assets.add_sass_to_fragment( - fragment, - "ProblemBlockDisplayyyy.scss", - ) - - def test_static_file_missing_raises_improperly_configured(self): - fragment = Fragment() - with patch.object(builtin_assets, 'get_static_file_url', lambda _path: None): - with self.assertRaises(ImproperlyConfigured): - builtin_assets.add_sass_to_fragment( - fragment, - "ProblemBlockDisplay.scss", - ) - - def test_happy_path(self): - fragment = Fragment() - builtin_assets.add_sass_to_fragment(fragment, "ProblemBlockDisplay.scss") - assert fragment.resources[0] == FragmentResource( - kind='url', - data='/static/css/ProblemBlockDisplay.css', - mimetype='text/css', - placement='head', - ) - - class AddCssToFragmentTests(TestCase): """ Tests for add_css_to_fragment. diff --git a/xmodule/util/builtin_assets.py b/xmodule/util/builtin_assets.py index 76e1455a1db3..cb1fca721414 100644 --- a/xmodule/util/builtin_assets.py +++ b/xmodule/util/builtin_assets.py @@ -32,7 +32,7 @@ def add_css_to_fragment(fragment, css_relative_path): if not css_absolute_path.is_file(): raise FileNotFoundError(f"css file not found: {css_absolute_path}") css_static_path = Path('css-builtin-blocks') / css_relative_path.with_suffix('.css') - css_url = get_static_file_url(str(css_static_path)) # get_static_file_url is theme-aware. + css_url = get_static_file_url(str(css_static_path)) if not css_url: raise ImproperlyConfigured( f"Did not find static CSS file {css_static_path} in staticfiles storage. Perhaps it wasn't collected?" @@ -40,39 +40,6 @@ def add_css_to_fragment(fragment, css_relative_path): fragment.add_css_url(css_url) -def add_sass_to_fragment(fragment, sass_relative_path): - """ - Given a Sass path relative to xmodule/assets, add a compiled CSS URL to the fragment. - - Raises: - * ValueError if {sass_relative_path} is absolute or does not end in '.scss'. - * FileNotFoundError if edx-platform/xmodule/assets/{sass_relative_path} is missing. - * ImproperlyConfigured if the lookup of the static CSS URL fails. This could happen - if Sass wasn't compiled, CSS wasn't collected, or the staticfiles app is misconfigured. - - Notes: - * This function is theme-aware. That is: If a theme is enabled which provides a compiled - CSS file of the same name, then that CSS file will be used instead. - """ - if not isinstance(sass_relative_path, Path): - sass_relative_path = Path(sass_relative_path) - if sass_relative_path.is_absolute(): - raise ValueError(f"sass_relative_path should be relative; is absolute: {sass_relative_path}") - if sass_relative_path.suffix != '.scss': - raise ValueError(f"sass_relative_path should be .scss file; is: {sass_relative_path}") - sass_absolute_path = Path(settings.REPO_ROOT) / "xmodule" / "assets" / sass_relative_path - if not sass_absolute_path.is_file(): - raise FileNotFoundError(f"Sass not found: {sass_absolute_path}") - css_static_path = Path('css') / sass_relative_path.with_suffix('.css') - css_url = get_static_file_url(str(css_static_path)) # get_static_file_url is theme-aware. - if not css_url: - raise ImproperlyConfigured( - f"Did not find CSS file {css_static_path} (compiled from {sass_absolute_path}) " - f"in staticfiles storage. Perhaps it wasn't collected?" - ) - fragment.add_css_url(css_url) - - def add_webpack_js_to_fragment(fragment, bundle_name): """ Add all JS webpack chunks to the supplied fragment. diff --git a/xmodule/vertical_block.py b/xmodule/vertical_block.py index 81621a7a7b88..150e6310853e 100644 --- a/xmodule/vertical_block.py +++ b/xmodule/vertical_block.py @@ -9,6 +9,7 @@ from functools import reduce import pytz +from django.conf import settings from lxml import etree from openedx_filters.learning.filters import VerticalBlockChildRenderStarted, VerticalBlockRenderCompleted from web_fragments.fragment import Fragment @@ -43,7 +44,7 @@ class VerticalFields: discussion_enabled = Boolean( display_name=_("Enable in-context discussions for the Unit"), help=_("Add discussion for the Unit."), - default=True, + default=settings.FEATURES.get('IN_CONTEXT_DISCUSSION_ENABLED_DEFAULT', True), scope=Scope.settings, )