Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions openedx/core/djangoapps/content/search/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
searchable_doc_tags,
searchable_doc_tags_for_collection,
searchable_doc_units,
searchable_doc_subsections,
searchable_doc_sections,
)

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -497,6 +499,12 @@ 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))
container_type = lib_api.ContainerType(container_key.container_type)
match container_type:
case lib_api.ContainerType.Unit:
doc.update(searchable_doc_subsections(container_key))
case lib_api.ContainerType.Subsection:
doc.update(searchable_doc_sections(container_key))
docs.append(doc)
except Exception as err: # pylint: disable=broad-except
status_cb(f"Error indexing container {container.key}: {err}")
Expand Down Expand Up @@ -864,6 +872,24 @@ def upsert_item_units_index_docs(opaque_key: OpaqueKey):
_update_index_docs([doc])


def upsert_item_subsections_index_docs(opaque_key: OpaqueKey):
"""
Updates the subsections data in documents for the given Course/Library block
"""
doc = {Fields.id: meili_id_from_opaque_key(opaque_key)}
doc.update(searchable_doc_subsections(opaque_key))
_update_index_docs([doc])


def upsert_item_sections_index_docs(opaque_key: OpaqueKey):
"""
Updates the sections data in documents for the given Course/Library block
"""
doc = {Fields.id: meili_id_from_opaque_key(opaque_key)}
doc.update(searchable_doc_sections(opaque_key))
_update_index_docs([doc])


def upsert_collection_tags_index_docs(collection_key: LibraryCollectionLocator):
"""
Updates the tags data in documents for the given library collection
Expand Down
63 changes: 48 additions & 15 deletions openedx/core/djangoapps/content/search/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ class Fields:
units = "units"
units_display_name = "display_name"
units_key = "key"
# Subsections (dictionary) that this object belongs to.
subsections = "subsections"
subsections_display_name = "display_name"
subsections_key = "key"
# Sections (dictionary) that this object belongs to.
sections = "sections"
sections_display_name = "display_name"
sections_key = "key"

# The "content" field is a dictionary of arbitrary data, depending on the block_type.
# It comes from each XBlock's index_dictionary() method (if present) plus some processing.
Expand Down Expand Up @@ -376,9 +384,9 @@ def _collections_for_content_object(object_id: OpaqueKey) -> dict:
return result


def _units_for_content_object(object_id: OpaqueKey) -> dict:
def _containers_for_content_object(object_id: OpaqueKey, container_type: str) -> dict:
"""
Given an XBlock, course, library, etc., get the units for its index doc.
Given an XBlock, course, library, etc., get the containers that it is part of for its index doc.

e.g. for something in Units "UNIT_A" and "UNIT_B", this would return:
{
Expand All @@ -388,38 +396,41 @@ def _units_for_content_object(object_id: OpaqueKey) -> dict:
}
}

If the object is in no collections, returns:
If the object is in no containers, returns:
{
"collections": {
"units": {
"display_name": [],
"key": [],
},
}
"""
container_field = getattr(Fields, container_type)
container_display_name_field = getattr(Fields, f'{container_type}_display_name')
container_key_field = getattr(Fields, f'{container_type}_key')
result = {
Fields.units: {
Fields.units_display_name: [],
Fields.units_key: [],
container_field: {
container_display_name_field: [],
container_key_field: [],
}
}

# Gather the units associated with this object
units = None
containers = None
try:
if isinstance(object_id, UsageKey):
units = lib_api.get_containers_contains_component(object_id)
if isinstance(object_id, OpaqueKey):
containers = lib_api.get_containers_contains_component(object_id)
else:
log.warning(f"Unexpected key type for {object_id}")

except ObjectDoesNotExist:
log.warning(f"No library item found for {object_id}")

if not units:
if not containers:
return result

for unit in units:
result[Fields.units][Fields.units_display_name].append(unit.display_name)
result[Fields.units][Fields.units_key].append(str(unit.container_key))
for container in containers:
result[container_field][container_display_name_field].append(container.display_name)
result[container_field][container_key_field].append(str(container.container_key))

return result

Expand Down Expand Up @@ -521,7 +532,29 @@ def searchable_doc_units(opaque_key: OpaqueKey) -> dict:
like Meilisearch or Elasticsearch, with the units data for the given content object.
"""
doc = searchable_doc_for_key(opaque_key)
doc.update(_units_for_content_object(opaque_key))
doc.update(_containers_for_content_object(opaque_key, "units"))

return doc


def searchable_doc_sections(opaque_key: OpaqueKey) -> dict:
"""
Generate a dictionary document suitable for ingestion into a search engine
like Meilisearch or Elasticsearch, with the sections data for the given content object.
"""
doc = searchable_doc_for_key(opaque_key)
doc.update(_containers_for_content_object(opaque_key, "sections"))

return doc


def searchable_doc_subsections(opaque_key: OpaqueKey) -> dict:
"""
Generate a dictionary document suitable for ingestion into a search engine
like Meilisearch or Elasticsearch, with the subsections data for the given content object.
"""
doc = searchable_doc_for_key(opaque_key)
doc.update(_containers_for_content_object(opaque_key, "subsections"))

return doc

Expand Down
6 changes: 6 additions & 0 deletions openedx/core/djangoapps/content/search/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
upsert_collection_tags_index_docs,
upsert_item_collections_index_docs,
upsert_item_units_index_docs,
upsert_item_sections_index_docs,
upsert_item_subsections_index_docs,
)
from .tasks import (
delete_library_block_index_doc,
Expand Down Expand Up @@ -266,6 +268,10 @@ def content_object_associations_changed_handler(**kwargs) -> None:
upsert_item_collections_index_docs(opaque_key)
if not content_object.changes or "units" in content_object.changes:
upsert_item_units_index_docs(opaque_key)
if not content_object.changes or "sections" in content_object.changes:
upsert_item_sections_index_docs(opaque_key)
if not content_object.changes or "subsections" in content_object.changes:
upsert_item_subsections_index_docs(opaque_key)


@receiver(LIBRARY_CONTAINER_CREATED)
Expand Down
18 changes: 10 additions & 8 deletions openedx/core/djangoapps/content_libraries/api/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from uuid import uuid4

from django.utils.text import slugify
from opaque_keys.edx.keys import UsageKeyV2
from opaque_keys.edx.keys import OpaqueKey, UsageKeyV2
from opaque_keys.edx.locator import LibraryContainerLocator, LibraryLocatorV2, LibraryUsageLocatorV2
from openedx_events.content_authoring.data import (
ContentObjectChangedData,
Expand Down Expand Up @@ -561,16 +561,18 @@ def update_container_children(


def get_containers_contains_component(
usage_key: LibraryUsageLocatorV2
usage_key: OpaqueKey
) -> list[ContainerMetadata]:
"""
Get containers that contains the component.
Get containers that contains the object.
"""
assert isinstance(usage_key, LibraryUsageLocatorV2)
component = get_component_from_usage_key(usage_key)
containers = authoring_api.get_containers_with_entity(
component.publishable_entity.pk,
)
if isinstance(usage_key, LibraryUsageLocatorV2):
component = get_component_from_usage_key(usage_key)
entity_id = component.publishable_entity.pk
elif isinstance(usage_key, LibraryContainerLocator):
container = _get_container_from_key(usage_key)
entity_id = container.publishable_entity.pk
containers = authoring_api.get_containers_with_entity(entity_id)
return [
ContainerMetadata.from_container(usage_key.context_key, container)
for container in containers
Expand Down
Loading