-
Notifications
You must be signed in to change notification settings - Fork 4.2k
feat: collections support for containers [FC-0083] #36504
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
3ad01b2
057cb37
20c307b
9aa8c37
43fb533
881292f
e8fd27d
8d92de4
68204ba
ee9347f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,35 +17,37 @@ | |
| from meilisearch import Client as MeilisearchClient | ||
| from meilisearch.errors import MeilisearchApiError, MeilisearchError | ||
| from meilisearch.models.task import TaskInfo | ||
| from opaque_keys.edx.keys import UsageKey, OpaqueKey | ||
| from opaque_keys.edx.locator import LibraryContainerLocator, LibraryLocatorV2, LibraryCollectionLocator | ||
| from opaque_keys import OpaqueKey | ||
| from opaque_keys.edx.keys import UsageKey | ||
| from opaque_keys.edx.locator import LibraryCollectionLocator, LibraryContainerLocator, LibraryLocatorV2 | ||
| from openedx_learning.api import authoring as authoring_api | ||
| from common.djangoapps.student.roles import GlobalStaff | ||
| from rest_framework.request import Request | ||
|
|
||
| from common.djangoapps.student.role_helpers import get_course_roles | ||
| from common.djangoapps.student.roles import GlobalStaff | ||
| from openedx.core.djangoapps.content.course_overviews.models import CourseOverview | ||
| from openedx.core.djangoapps.content.search.models import get_access_ids_for_request, IncrementalIndexCompleted | ||
| from openedx.core.djangoapps.content.search.index_config import ( | ||
| INDEX_DISTINCT_ATTRIBUTE, | ||
| INDEX_FILTERABLE_ATTRIBUTES, | ||
| INDEX_SEARCHABLE_ATTRIBUTES, | ||
| INDEX_SORTABLE_ATTRIBUTES, | ||
| INDEX_RANKING_RULES, | ||
| INDEX_SEARCHABLE_ATTRIBUTES, | ||
| INDEX_SORTABLE_ATTRIBUTES | ||
| ) | ||
| from openedx.core.djangoapps.content.search.models import IncrementalIndexCompleted, get_access_ids_for_request | ||
| from openedx.core.djangoapps.content_libraries import api as lib_api | ||
| from xmodule.modulestore.django import modulestore | ||
|
|
||
| from .documents import ( | ||
| Fields, | ||
| meili_id_from_opaque_key, | ||
| searchable_doc_for_course_block, | ||
| searchable_doc_collections, | ||
| searchable_doc_for_collection, | ||
| searchable_doc_for_container, | ||
| searchable_doc_for_course_block, | ||
| searchable_doc_for_library_block, | ||
| searchable_doc_for_key, | ||
| searchable_doc_collections, | ||
| searchable_doc_tags, | ||
| searchable_doc_tags_for_collection, | ||
| searchable_doc_tags_for_collection | ||
| ) | ||
|
|
||
| log = logging.getLogger(__name__) | ||
|
|
@@ -487,6 +489,7 @@ def index_container_batch(batch, num_done, library_key) -> int: | |
| ) | ||
| doc = searchable_doc_for_container(container_key) | ||
| doc.update(searchable_doc_tags(container_key)) | ||
| doc.update(searchable_doc_collections(container_key)) | ||
| docs.append(doc) | ||
| except Exception as err: # pylint: disable=broad-except | ||
| status_cb(f"Error indexing container {container.key}: {err}") | ||
|
|
@@ -709,15 +712,15 @@ def upsert_library_collection_index_doc(library_key: LibraryLocatorV2, collectio | |
| If the Collection is not found or disabled (i.e. soft-deleted), then delete it from the search index. | ||
| """ | ||
| doc = searchable_doc_for_collection(library_key, collection_key) | ||
| update_components = False | ||
| update_items = False | ||
|
|
||
| # Soft-deleted/disabled collections are removed from the index | ||
| # and their components updated. | ||
| if doc.get('_disabled'): | ||
|
|
||
| _delete_index_doc(doc[Fields.id]) | ||
|
|
||
| update_components = True | ||
| update_items = True | ||
|
|
||
| # Hard-deleted collections are also deleted from the index, | ||
| # but their components are automatically updated as part of the deletion process, so we don't have to. | ||
|
|
@@ -730,15 +733,17 @@ def upsert_library_collection_index_doc(library_key: LibraryLocatorV2, collectio | |
| else: | ||
| already_indexed = _get_document_from_index(doc[Fields.id]) | ||
| if not already_indexed: | ||
| update_components = True | ||
| update_items = True | ||
|
|
||
| _update_index_docs([doc]) | ||
|
|
||
| # Asynchronously update the collection's components "collections" field | ||
| if update_components: | ||
| from .tasks import update_library_components_collections as update_task | ||
| if update_items: | ||
| from .tasks import update_library_components_collections as update_components_task | ||
| from .tasks import update_library_containers_collections as update_containers_task | ||
|
|
||
| update_task.delay(str(library_key), collection_key) | ||
| update_components_task.delay(str(library_key), collection_key) | ||
| update_containers_task.delay(str(library_key), collection_key) | ||
|
|
||
|
|
||
| def update_library_components_collections( | ||
|
|
@@ -773,6 +778,38 @@ def update_library_components_collections( | |
| _update_index_docs(docs) | ||
|
|
||
|
|
||
| def update_library_containers_collections( | ||
| library_key: LibraryLocatorV2, | ||
| collection_key: str, | ||
|
||
| batch_size: int = 1000, | ||
| ) -> None: | ||
| """ | ||
| Updates the "collections" field for all containers associated with a given Library Collection. | ||
|
|
||
| Because there may be a lot of containers, we send these updates to Meilisearch in batches. | ||
| """ | ||
| library = lib_api.get_library(library_key) | ||
| containers = authoring_api.get_collection_containers(library.learning_package_id, collection_key) | ||
|
|
||
| paginator = Paginator(containers, batch_size) | ||
| for page in paginator.page_range: | ||
| docs = [] | ||
|
|
||
| for container in paginator.page(page).object_list: | ||
| container_key = lib_api.library_container_locator( | ||
| library_key, | ||
| container, | ||
| ) | ||
| doc = searchable_doc_collections(container_key) | ||
| docs.append(doc) | ||
|
|
||
| log.info( | ||
| f"Updating document.collections for library {library_key} containers" | ||
| f" page {page} / {paginator.num_pages}" | ||
| ) | ||
| _update_index_docs(docs) | ||
|
|
||
|
|
||
| def upsert_library_container_index_doc(container_key: LibraryContainerLocator) -> None: | ||
| """ | ||
| Creates, updates, or deletes the document for the given Library Container in the search index. | ||
|
|
@@ -819,12 +856,12 @@ def upsert_content_object_tags_index_doc(key: OpaqueKey): | |
| _update_index_docs([doc]) | ||
|
|
||
|
|
||
| def upsert_block_collections_index_docs(usage_key: UsageKey): | ||
| def upsert_item_collections_index_docs(opaque_key: OpaqueKey): | ||
| """ | ||
| Updates the collections data in documents for the given Course/Library block | ||
| Updates the collections data in documents for the given Course/Library block, or Container | ||
| """ | ||
| doc = {Fields.id: meili_id_from_opaque_key(usage_key)} | ||
| doc.update(searchable_doc_collections(usage_key)) | ||
| doc = {Fields.id: meili_id_from_opaque_key(opaque_key)} | ||
| doc.update(searchable_doc_collections(opaque_key)) | ||
| _update_index_docs([doc]) | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -309,7 +309,7 @@ def _tags_for_content_object(object_id: OpaqueKey) -> dict: | |||||
| return {Fields.tags: result} | ||||||
|
|
||||||
|
|
||||||
| def _collections_for_content_object(object_id: UsageKey | LearningContextKey) -> dict: | ||||||
| def _collections_for_content_object(object_id: OpaqueKey) -> dict: | ||||||
| """ | ||||||
| Given an XBlock, course, library, etc., get the collections for its index doc. | ||||||
|
|
||||||
|
|
@@ -340,11 +340,21 @@ def _collections_for_content_object(object_id: UsageKey | LearningContextKey) -> | |||||
| # Gather the collections associated with this object | ||||||
| collections = None | ||||||
| try: | ||||||
| component = lib_api.get_component_from_usage_key(object_id) | ||||||
| collections = authoring_api.get_entity_collections( | ||||||
| component.learning_package_id, | ||||||
| component.key, | ||||||
| ) | ||||||
| if isinstance(object_id, UsageKey): | ||||||
| component = lib_api.get_component_from_usage_key(object_id) | ||||||
| collections = authoring_api.get_entity_collections( | ||||||
| component.learning_package_id, | ||||||
| component.key, | ||||||
| ) | ||||||
| elif isinstance(object_id, LibraryContainerLocator): | ||||||
| container = lib_api.get_container_from_key(object_id) | ||||||
| collections = authoring_api.get_entity_collections( | ||||||
| container.publishable_entity.learning_package_id, | ||||||
| container.key, | ||||||
| ) | ||||||
| else: | ||||||
| return result | ||||||
|
||||||
| else: | |
| return result |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right! Fixed here: 20c307b
rpenido marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -116,6 +116,19 @@ def update_library_components_collections(library_key_str: str, collection_key: | |
| api.update_library_components_collections(library_key, collection_key) | ||
|
|
||
|
|
||
| @shared_task(base=LoggedTask, autoretry_for=(MeilisearchError, ConnectionError)) | ||
| @set_code_owner_attribute | ||
| def update_library_containers_collections(library_key_str: str, collection_key: str) -> None: | ||
|
||
| """ | ||
| Celery task to update the "collections" field for containers in the given content library collection. | ||
| """ | ||
| library_key = LibraryLocatorV2.from_string(library_key_str) | ||
|
|
||
| log.info("Updating document.collections for library %s collection %s containers", library_key, collection_key) | ||
|
|
||
| api.update_library_containers_collections(library_key, collection_key) | ||
|
|
||
|
|
||
| @shared_task(base=LoggedTask, autoretry_for=(MeilisearchError, ConnectionError)) | ||
| @set_code_owner_attribute | ||
| def update_library_container_index_doc(library_key_str: str, container_key_str: str) -> None: | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.