fix: updating search index on container delete and restore (#36956)

Update children and parents index data on container delete and restore.
This commit is contained in:
Navin Karkera
2025-06-30 19:59:07 +02:00
committed by GitHub
parent a3f322f2a4
commit b27c41cc8c
2 changed files with 153 additions and 13 deletions

View File

@@ -237,6 +237,11 @@ class TestSearchApi(ModuleStoreTestCase):
title="Subsection 1",
user_id=None,
)
library_api.update_container_children(
self.subsection.container_key,
[self.unit.container_key],
None,
)
self.subsection_key = "lct:org1:lib:subsection:subsection-1"
self.section = library_api.create_container(
self.library.key,
@@ -246,6 +251,11 @@ class TestSearchApi(ModuleStoreTestCase):
user_id=None,
)
self.section_key = "lct:org1:lib:section:section-1"
library_api.update_container_children(
self.section.container_key,
[self.subsection.container_key],
None,
)
self.unit_dict = {
"id": "lctorg1libunitunit-1-e4527f7c",
@@ -278,10 +288,10 @@ class TestSearchApi(ModuleStoreTestCase):
"type": "library_container",
"display_name": "Subsection 1",
# description is not set for containers
"num_children": 0,
"num_children": 1,
"content": {
"child_usage_keys": [],
"child_display_names": [],
"child_usage_keys": ["lct:org1:lib:unit:unit-1"],
"child_display_names": ["Unit 1"],
},
"publish_status": "never",
"context_key": "lib:org1:lib",
@@ -301,10 +311,10 @@ class TestSearchApi(ModuleStoreTestCase):
"type": "library_container",
"display_name": "Section 1",
# description is not set for containers
"num_children": 0,
"num_children": 1,
"content": {
"child_usage_keys": [],
"child_display_names": [],
"child_usage_keys": ["lct:org1:lib:subsection:subsection-1"],
"child_display_names": ["Subsection 1"],
},
"publish_status": "never",
"context_key": "lib:org1:lib",
@@ -345,11 +355,11 @@ class TestSearchApi(ModuleStoreTestCase):
doc_unit = copy.deepcopy(self.unit_dict)
doc_unit["tags"] = {}
doc_unit["collections"] = {'display_name': [], 'key': []}
doc_unit["subsections"] = {"display_name": [], "key": []}
doc_unit["subsections"] = {'display_name': ['Subsection 1'], 'key': ['lct:org1:lib:subsection:subsection-1']}
doc_subsection = copy.deepcopy(self.subsection_dict)
doc_subsection["tags"] = {}
doc_subsection["collections"] = {'display_name': [], 'key': []}
doc_subsection["sections"] = {'display_name': [], 'key': []}
doc_subsection["sections"] = {'display_name': ['Section 1'], 'key': ['lct:org1:lib:section:section-1']}
doc_section = copy.deepcopy(self.section_dict)
doc_section["tags"] = {}
doc_section["collections"] = {'display_name': [], 'key': []}
@@ -387,11 +397,11 @@ class TestSearchApi(ModuleStoreTestCase):
doc_unit = copy.deepcopy(self.unit_dict)
doc_unit["tags"] = {}
doc_unit["collections"] = {"display_name": [], "key": []}
doc_unit["subsections"] = {"display_name": [], "key": []}
doc_unit["subsections"] = {'display_name': ['Subsection 1'], 'key': ['lct:org1:lib:subsection:subsection-1']}
doc_subsection = copy.deepcopy(self.subsection_dict)
doc_subsection["tags"] = {}
doc_subsection["collections"] = {'display_name': [], 'key': []}
doc_subsection["sections"] = {'display_name': [], 'key': []}
doc_subsection["sections"] = {'display_name': ['Section 1'], 'key': ['lct:org1:lib:section:section-1']}
doc_section = copy.deepcopy(self.section_dict)
doc_section["tags"] = {}
doc_section["collections"] = {'display_name': [], 'key': []}
@@ -989,12 +999,70 @@ class TestSearchApi(ModuleStoreTestCase):
"""
container = getattr(self, container_type)
container_dict = getattr(self, f"{container_type}_dict")
update_doc_calls = []
def clear_contents(data: dict):
return {
**data,
"num_children": 0,
"content": {
"child_usage_keys": [],
"child_display_names": [],
},
}
if container_type == "unit":
update_doc_calls.append(call([clear_contents(self.subsection_dict)]))
elif container_type == "subsection":
update_doc_calls.append(call([clear_contents(self.section_dict)]))
update_doc_calls.append(call([{
'id': self.unit_dict['id'],
'subsections': {'display_name': [], 'key': []},
}]))
elif container_type == "section":
update_doc_calls.append(call([{
'id': self.subsection_dict['id'],
'sections': {'display_name': [], 'key': []},
}]))
library_api.delete_container(container.container_key)
mock_meilisearch.return_value.index.return_value.delete_document.assert_called_once_with(
container_dict["id"],
)
# Parent containers index data is updated.
if update_doc_calls:
mock_meilisearch.return_value.index.return_value.update_documents.assert_has_calls(
update_doc_calls,
any_order=True,
)
# Restore
library_api.restore_container(container.container_key)
if container_type == "unit":
update_doc_calls.append(call([self.subsection_dict]))
elif container_type == "subsection":
update_doc_calls.append(call([self.section_dict]))
update_doc_calls.append(call([{
'id': self.unit_dict['id'],
'subsections': {
'display_name': [self.subsection_dict['display_name']],
'key': [self.subsection_key],
},
}]))
elif container_type == "section":
update_doc_calls.append(call([{
'id': self.subsection_dict['id'],
'sections': {
'display_name': [self.section_dict['display_name']],
'key': [self.section_key],
},
}]))
# Parent containers index data is updated on restore again.
if update_doc_calls:
mock_meilisearch.return_value.index.return_value.update_documents.assert_has_calls(
update_doc_calls,
any_order=True,
)
@ddt.data(
"unit",

View File

@@ -308,6 +308,12 @@ def update_container(
version: ContainerVersion
affected_containers: list[ContainerMetadata] = []
# Get children containers or components to update their index data
children = get_container_children(
container_key,
published=False,
)
child_key_name = 'container_key'
match container_type:
case ContainerType.Unit:
@@ -318,6 +324,8 @@ def update_container(
created_by=user_id,
)
affected_containers = get_containers_contains_item(container_key)
# Components have usage_key instead of container_key
child_key_name = 'usage_key'
case ContainerType.Subsection:
version = authoring_api.create_next_subsection_version(
container.subsection,
@@ -354,6 +362,16 @@ def update_container(
container_key=affected_container.container_key,
)
)
# Update children components and containers index data, for example,
# All subsections under a section have section key in index that needs to be updated.
# So if parent section name has been changed, it needs to be reflected in sections key of children
for child in children:
CONTENT_OBJECT_ASSOCIATIONS_CHANGED.send_event(
content_object=ContentObjectChangedData(
object_id=str(getattr(child, child_key_name)),
changes=[container_key.container_type + "s"],
),
)
return ContainerMetadata.from_container(library_key, version.container)
@@ -369,11 +387,17 @@ def delete_container(
library_key = container_key.lib_key
container = _get_container_from_key(container_key)
affected_containers = get_containers_contains_item(container_key)
# Fetch related collections and containers before soft-delete
affected_collections = authoring_api.get_entity_collections(
container.publishable_entity.learning_package_id,
container.key,
)
affected_containers = get_containers_contains_item(container_key)
# Get children containers or components to update their index data
children = get_container_children(
container_key,
published=False,
)
authoring_api.soft_delete_draft(container.pk)
LIBRARY_CONTAINER_DELETED.send_event(
@@ -404,6 +428,21 @@ def delete_container(
container_key=affected_container.container_key,
)
)
container_type = ContainerType(container_key.container_type)
key_name = 'container_key'
if container_type == ContainerType.Unit:
# Components have usage_key instead of container_key
key_name = 'usage_key'
# Update children components and containers index data, for example,
# All subsections under a section have section key in index that needs to be updated.
# So if parent section is deleted, it needs to be removed from sections key of children
for child in children:
CONTENT_OBJECT_ASSOCIATIONS_CHANGED.send_event(
content_object=ContentObjectChangedData(
object_id=str(getattr(child, key_name)),
changes=[container_key.container_type + "s"],
),
)
def restore_container(container_key: LibraryContainerLocator) -> None:
@@ -419,6 +458,13 @@ def restore_container(container_key: LibraryContainerLocator) -> None:
)
authoring_api.set_draft_version(container.pk, container.versioning.latest.pk)
# Fetch related containers after restore
affected_containers = get_containers_contains_item(container_key)
# Get children containers or components to update their index data
children = get_container_children(
container_key,
published=False,
)
LIBRARY_CONTAINER_CREATED.send_event(
library_container=LibraryContainerData(
@@ -426,11 +472,15 @@ def restore_container(container_key: LibraryContainerLocator) -> None:
)
)
# Add tags and collections back to index
content_changes = ["collections", "tags"]
if affected_containers and len(affected_containers) > 0:
# Update parent key data in index. Eg. `sections` key in index for subsection
content_changes.append(str(affected_containers[0].container_type.value) + "s")
# Add tags, collections and parent data back to index
CONTENT_OBJECT_ASSOCIATIONS_CHANGED.send_event(
content_object=ContentObjectChangedData(
object_id=str(container_key),
changes=["collections", "tags"],
changes=content_changes,
),
)
@@ -447,6 +497,28 @@ def restore_container(container_key: LibraryContainerLocator) -> None:
),
)
)
# Send events related to the containers that contains the updated container.
# This is to update the children display names used in the section/subsection previews.
for affected_container in affected_containers:
LIBRARY_CONTAINER_UPDATED.send_event(
library_container=LibraryContainerData(
container_key=affected_container.container_key,
)
)
container_type = ContainerType(container_key.container_type)
key_name = 'container_key'
if container_type == ContainerType.Unit:
key_name = 'usage_key'
# Update children components and containers index data, for example,
# All subsections under a section have section key in index that needs to be updated.
# Should restore removed parent section in sections key of children subsections
for child in children:
CONTENT_OBJECT_ASSOCIATIONS_CHANGED.send_event(
content_object=ContentObjectChangedData(
object_id=str(getattr(child, key_name)),
changes=[container_key.container_type + "s"],
),
)
def get_container_children(