From dd789886b60b74d85578d00758ed8b40c0a4e7eb Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Wed, 18 Sep 2024 19:03:09 +0930 Subject: [PATCH] feat: remove soft-deleted collections from the Studio search index --- openedx/core/djangoapps/content/search/api.py | 32 ++++++++- .../content/search/tests/test_api.py | 69 +++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/openedx/core/djangoapps/content/search/api.py b/openedx/core/djangoapps/content/search/api.py index 71d09590d003..267c65cd6026 100644 --- a/openedx/core/djangoapps/content/search/api.py +++ b/openedx/core/djangoapps/content/search/api.py @@ -256,6 +256,28 @@ def _update_index_docs(docs) -> None: _wait_for_meili_tasks(tasks) +def _delete_index_docs(docs) -> None: + """ + Helper function that deletes the given documents from the search index + + If there is a rebuild in progress, the document will also be removed from the new index. + """ + if not docs: + return + + client = _get_meilisearch_client() + current_rebuild_index_name = _get_running_rebuild_index_name() + + tasks = [] + if current_rebuild_index_name: + # If there is a rebuild in progress, the document will also be removed from the new index. + tasks.append(client.index(current_rebuild_index_name).delete_documents(docs)) + + tasks.append(client.index(STUDIO_INDEX_NAME).delete_documents(docs)) + + _wait_for_meili_tasks(tasks) + + def only_if_meilisearch_enabled(f): """ Only call `f` if meilisearch is enabled @@ -563,18 +585,24 @@ def upsert_library_block_index_doc(usage_key: UsageKey) -> None: def upsert_library_collection_index_doc(library_key: LibraryLocatorV2, collection_key: str) -> None: """ - Creates or updates the document for the given Library Collection in the search index + Creates, updates, or deletes the document for the given Library Collection in the search index. + + If the Collection is disabled (i.e. soft-deleted), then delete it from the search index. """ content_library = lib_api.ContentLibrary.objects.get_by_key(library_key) collection = authoring_api.get_collection( learning_package_id=content_library.learning_package_id, collection_key=collection_key, ) + docs = [ searchable_doc_for_collection(collection) ] + if collection.id and collection.enabled: + _update_index_docs(docs) - _update_index_docs(docs) + else: + _delete_index_docs(docs) def upsert_content_library_index_docs(library_key: LibraryLocatorV2) -> None: diff --git a/openedx/core/djangoapps/content/search/tests/test_api.py b/openedx/core/djangoapps/content/search/tests/test_api.py index 4aa41a156dab..21d262fdef5b 100644 --- a/openedx/core/djangoapps/content/search/tests/test_api.py +++ b/openedx/core/djangoapps/content/search/tests/test_api.py @@ -615,3 +615,72 @@ def test_index_tags_in_collections(self, mock_meilisearch): ], any_order=True, ) + + @override_settings(MEILISEARCH_ENABLED=True) + def test_delete_collection(self, mock_meilisearch): + # Add a component to the collection + updated_date = datetime(2023, 6, 7, 8, 9, 10, tzinfo=timezone.utc) + with freeze_time(updated_date): + library_api.update_library_collection_components( + self.library.key, + collection_key=self.collection.key, + usage_keys=[ + self.problem1.usage_key, + ], + ) + + doc_collection = copy.deepcopy(self.collection_dict) + doc_collection["num_children"] = 1 + doc_collection["modified"] = updated_date.timestamp() + doc_problem_with_collection = { + "id": self.doc_problem1["id"], + "collections": { + "display_name": [self.collection.title], + "key": [self.collection.key], + }, + } + assert mock_meilisearch.return_value.index.return_value.update_documents.call_count == 2 + mock_meilisearch.return_value.index.return_value.update_documents.assert_has_calls( + [ + call([doc_collection]), + call([doc_problem_with_collection]), + ], + any_order=True, + ) + mock_meilisearch.return_value.index.reset_mock() + + # Soft-delete the collection + deleted_date = datetime(2023, 7, 8, 9, 10, 11, tzinfo=timezone.utc) + with freeze_time(deleted_date): + authoring_api.delete_collection( + self.collection.learning_package_id, + self.collection.key, + ) + + doc_collection["modified"] = deleted_date.timestamp() + doc_problem_without_collection = { + "id": self.doc_problem1["id"], + "collections": {}, + } + + mock_meilisearch.return_value.index.return_value.delete_documents.assert_called_once_with([doc_collection]) + ## TODO: how/where to update the components in a soft-deleted collection? + # mock_meilisearch.return_value.index.return_value.update_documents.assert_called_once_with([ + # doc_problem_without_collection + # ]) + + # Restore the collection + restored_date = datetime(2023, 8, 9, 10, 11, 12, tzinfo=timezone.utc) + with freeze_time(restored_date): + authoring_api.restore_collection( + self.collection.learning_package_id, + self.collection.key, + ) + + doc_collection["modified"] = restored_date.timestamp() + + mock_meilisearch.return_value.index.return_value.update_documents.assert_called_once_with([doc_collection]) + ## TODO: how/where to update the components in a restored collection? + # mock_meilisearch.return_value.index.return_value.update_documents.assert_called_once_with([ + # doc_problem_with_collection + # ])