Skip to content

Commit 9ae1813

Browse files
ChrisChVirtazaakram
authored andcommitted
feat: Update containers in search index on components update/delete [FC-0083] (#36432)
* feat: Added get_containers_contains_component in containers api with tests * feat: Add publish_status to containers search document * feat: Add LIBRARY_CONTAINER_UPDATED whend deleted a component inside a container * feat: Send LIBRARY_CONTAINER_UPDATED signal when updating component of container * fix: Bugs sending LIBRARY_CONTAINER_UPDATED signal * feat: Add publish_status of container as PublishStatus.Never by default * refactor: ContentLibraryContainersTest to use update_container_children to add components * style: Clean code after fix conflicts * fix: Broken lint * fix: lint
1 parent 8e2b576 commit 9ae1813

7 files changed

Lines changed: 184 additions & 2 deletions

File tree

openedx/core/djangoapps/content/search/documents.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ def searchable_doc_for_container(
572572
Fields.usage_key: str(container_key), # Field name isn't exact but this is the closest match
573573
Fields.block_id: container_key.container_id, # Field name isn't exact but this is the closest match
574574
Fields.access_id: _meili_access_id_from_context_key(container_key.library_key),
575+
Fields.publish_status: PublishStatus.never,
575576
}
576577

577578
try:

openedx/core/djangoapps/content_libraries/api/containers.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
LibraryContainerLocator,
1414
LibraryLocatorV2,
1515
UsageKeyV2,
16+
LibraryUsageLocatorV2,
1617
)
1718
from openedx_events.content_authoring.data import LibraryContainerData
1819
from openedx_events.content_authoring.signals import (
@@ -42,6 +43,7 @@
4243
"update_container",
4344
"delete_container",
4445
"update_container_children",
46+
"get_containers_contains_component",
4547
]
4648

4749

@@ -316,3 +318,20 @@ def update_container_children(
316318
)
317319

318320
return ContainerMetadata.from_container(library_key, new_version.container)
321+
322+
323+
def get_containers_contains_component(
324+
usage_key: LibraryUsageLocatorV2
325+
) -> list[ContainerMetadata]:
326+
"""
327+
Get containers that contains the component.
328+
"""
329+
assert isinstance(usage_key, LibraryUsageLocatorV2)
330+
component = get_component_from_usage_key(usage_key)
331+
containers = authoring_api.get_containers_with_entity(
332+
component.publishable_entity.pk,
333+
)
334+
return [
335+
ContainerMetadata.from_container(usage_key.context_key, container)
336+
for container in containers
337+
]

openedx/core/djangoapps/content_libraries/api/libraries.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
ContentLibraryData,
8383
LibraryBlockData,
8484
LibraryCollectionData,
85+
LibraryContainerData,
8586
ContentObjectChangedData,
8687
)
8788
from openedx_events.content_authoring.signals import (
@@ -92,6 +93,7 @@
9293
LIBRARY_BLOCK_DELETED,
9394
LIBRARY_BLOCK_UPDATED,
9495
LIBRARY_COLLECTION_UPDATED,
96+
LIBRARY_CONTAINER_UPDATED,
9597
CONTENT_OBJECT_ASSOCIATIONS_CHANGED,
9698
)
9799
from openedx_learning.api import authoring as authoring_api
@@ -113,6 +115,7 @@
113115
xblock_type_display_name,
114116
)
115117
from openedx.core.lib.xblock_serializer.api import serialize_modulestore_block_for_learning_core
118+
from openedx.core.djangoapps.content_libraries import api as lib_api
116119
from openedx.core.types import User as UserType
117120
from xmodule.modulestore.django import modulestore
118121

@@ -901,6 +904,18 @@ def set_library_block_olx(usage_key: LibraryUsageLocatorV2, new_olx_str: str) ->
901904
)
902905
)
903906

907+
# For each container, trigger LIBRARY_CONTAINER_UPDATED signal and set background=True to trigger
908+
# container indexing asynchronously.
909+
affected_containers = lib_api.get_containers_contains_component(usage_key)
910+
for container in affected_containers:
911+
LIBRARY_CONTAINER_UPDATED.send_event(
912+
library_container=LibraryContainerData(
913+
library_key=usage_key.lib_key,
914+
container_key=str(container.container_key),
915+
background=True,
916+
)
917+
)
918+
904919
return new_component_version
905920

906921

@@ -1205,6 +1220,7 @@ def delete_library_block(usage_key: LibraryUsageLocatorV2, remove_from_parent=Tr
12051220
component = get_component_from_usage_key(usage_key)
12061221
library_key = usage_key.context_key
12071222
affected_collections = authoring_api.get_entity_collections(component.learning_package_id, component.key)
1223+
affected_containers = lib_api.get_containers_contains_component(usage_key)
12081224

12091225
authoring_api.soft_delete_draft(component.pk)
12101226

@@ -1228,6 +1244,19 @@ def delete_library_block(usage_key: LibraryUsageLocatorV2, remove_from_parent=Tr
12281244
)
12291245
)
12301246

1247+
# For each container, trigger LIBRARY_CONTAINER_UPDATED signal and set background=True to trigger
1248+
# container indexing asynchronously.
1249+
#
1250+
# To update the components count in containers
1251+
for container in affected_containers:
1252+
LIBRARY_CONTAINER_UPDATED.send_event(
1253+
library_container=LibraryContainerData(
1254+
library_key=library_key,
1255+
container_key=str(container.container_key),
1256+
background=True,
1257+
)
1258+
)
1259+
12311260

12321261
def restore_library_block(usage_key: LibraryUsageLocatorV2) -> None:
12331262
"""

openedx/core/djangoapps/content_libraries/library_context.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from django.core.exceptions import PermissionDenied
77
from rest_framework.exceptions import NotFound
88

9-
from openedx_events.content_authoring.data import LibraryBlockData
10-
from openedx_events.content_authoring.signals import LIBRARY_BLOCK_UPDATED
9+
from openedx_events.content_authoring.data import LibraryBlockData, LibraryContainerData
10+
from openedx_events.content_authoring.signals import LIBRARY_BLOCK_UPDATED, LIBRARY_CONTAINER_UPDATED
1111
from opaque_keys.edx.keys import UsageKeyV2
1212
from opaque_keys.edx.locator import LibraryUsageLocatorV2, LibraryLocatorV2
1313
from openedx_learning.api import authoring as authoring_api
@@ -114,3 +114,19 @@ def send_block_updated_event(self, usage_key: UsageKeyV2):
114114
usage_key=usage_key,
115115
)
116116
)
117+
118+
def send_container_updated_events(self, usage_key: UsageKeyV2):
119+
"""
120+
Send "container updated" events for containers that contains the library block
121+
with the given usage_key.
122+
"""
123+
assert isinstance(usage_key, LibraryUsageLocatorV2)
124+
affected_containers = api.get_containers_contains_component(usage_key)
125+
for container in affected_containers:
126+
LIBRARY_CONTAINER_UPDATED.send_event(
127+
library_container=LibraryContainerData(
128+
library_key=usage_key.lib_key,
129+
container_key=str(container.container_key),
130+
background=True,
131+
)
132+
)

openedx/core/djangoapps/content_libraries/tests/test_api.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
from openedx_events.content_authoring.data import (
1717
ContentObjectChangedData,
1818
LibraryCollectionData,
19+
LibraryContainerData,
1920
)
2021
from openedx_events.content_authoring.signals import (
2122
CONTENT_OBJECT_ASSOCIATIONS_CHANGED,
2223
LIBRARY_COLLECTION_CREATED,
2324
LIBRARY_COLLECTION_DELETED,
2425
LIBRARY_COLLECTION_UPDATED,
26+
LIBRARY_CONTAINER_UPDATED,
2527
)
2628
from openedx_events.tests.utils import OpenEdxEventsTestMixin
2729
from openedx_learning.api import authoring as authoring_api
@@ -742,3 +744,109 @@ def test_delete_component_and_revert(self):
742744
},
743745
event_receiver.call_args_list[1].kwargs,
744746
)
747+
748+
749+
class ContentLibraryContainersTest(ContentLibrariesRestApiTest, TestCase):
750+
"""
751+
Tests for Content Library API containers methods.
752+
"""
753+
def setUp(self):
754+
super().setUp()
755+
756+
# Create Content Libraries
757+
self._create_library("test-lib-cont-1", "Test Library 1")
758+
759+
# Fetch the created ContentLibrare objects so we can access their learning_package.id
760+
self.lib1 = ContentLibrary.objects.get(slug="test-lib-cont-1")
761+
762+
# Create Units
763+
self.unit1 = api.create_container(self.lib1.library_key, api.ContainerType.Unit, 'unit-1', 'Unit 1', None)
764+
self.unit2 = api.create_container(self.lib1.library_key, api.ContainerType.Unit, 'unit-2', 'Unit 2', None)
765+
766+
# Create XBlocks
767+
# Create some library blocks in lib1
768+
self.problem_block = self._add_block_to_library(
769+
self.lib1.library_key, "problem", "problem1",
770+
)
771+
self.problem_block_usage_key = UsageKey.from_string(self.problem_block["id"])
772+
self.html_block = self._add_block_to_library(
773+
self.lib1.library_key, "html", "html1",
774+
)
775+
self.html_block_usage_key = UsageKey.from_string(self.html_block["id"])
776+
777+
# Add content to units
778+
api.update_container_children(
779+
self.unit1.container_key,
780+
[self.problem_block_usage_key, self.html_block_usage_key],
781+
None,
782+
)
783+
api.update_container_children(
784+
self.unit2.container_key,
785+
[self.html_block_usage_key],
786+
None,
787+
)
788+
789+
def test_get_containers_contains_component(self):
790+
problem_block_containers = api.get_containers_contains_component(self.problem_block_usage_key)
791+
html_block_containers = api.get_containers_contains_component(self.html_block_usage_key)
792+
793+
assert len(problem_block_containers) == 1
794+
assert problem_block_containers[0].container_key == self.unit1.container_key
795+
796+
assert len(html_block_containers) == 2
797+
assert html_block_containers[0].container_key == self.unit1.container_key
798+
assert html_block_containers[1].container_key == self.unit2.container_key
799+
800+
def _validate_calls_of_html_block(self, event_mock):
801+
"""
802+
Validate that the `event_mock` has been called twice
803+
using the `LIBRARY_CONTAINER_UPDATED` signal.
804+
"""
805+
assert event_mock.call_count == 2
806+
self.assertDictContainsSubset(
807+
{
808+
"signal": LIBRARY_CONTAINER_UPDATED,
809+
"sender": None,
810+
"library_container": LibraryContainerData(
811+
library_key=self.lib1.library_key,
812+
container_key=str(self.unit1.container_key),
813+
background=True,
814+
)
815+
},
816+
event_mock.call_args_list[0].kwargs,
817+
)
818+
self.assertDictContainsSubset(
819+
{
820+
"signal": LIBRARY_CONTAINER_UPDATED,
821+
"sender": None,
822+
"library_container": LibraryContainerData(
823+
library_key=self.lib1.library_key,
824+
container_key=str(self.unit2.container_key),
825+
background=True,
826+
)
827+
},
828+
event_mock.call_args_list[1].kwargs,
829+
)
830+
831+
def test_call_container_update_signal_when_delete_component(self):
832+
container_update_event_receiver = mock.Mock()
833+
LIBRARY_CONTAINER_UPDATED.connect(container_update_event_receiver)
834+
835+
api.delete_library_block(self.html_block_usage_key)
836+
self._validate_calls_of_html_block(container_update_event_receiver)
837+
838+
def test_call_container_update_signal_when_update_olx(self):
839+
block_olx = "<html><b>Hello world!</b></html>"
840+
container_update_event_receiver = mock.Mock()
841+
LIBRARY_CONTAINER_UPDATED.connect(container_update_event_receiver)
842+
843+
self._set_library_block_olx(self.html_block_usage_key, block_olx)
844+
self._validate_calls_of_html_block(container_update_event_receiver)
845+
846+
def test_call_container_update_signal_when_update_component(self):
847+
block_olx = "<html><b>Hello world!</b></html>"
848+
container_update_event_receiver = mock.Mock()
849+
LIBRARY_CONTAINER_UPDATED.connect(container_update_event_receiver)
850+
851+
self._set_library_block_fields(self.html_block_usage_key, {"data": block_olx, "metadata": {}})
852+
self._validate_calls_of_html_block(container_update_event_receiver)

openedx/core/djangoapps/xblock/learning_context/learning_context.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,11 @@ def send_block_updated_event(self, usage_key):
7676
7777
usage_key: the UsageKeyV2 subclass used for this learning context
7878
"""
79+
80+
def send_container_updated_events(self, usage_key):
81+
"""
82+
Send "container updated" events for containers that contains the block with
83+
the given usage_key in this context.
84+
85+
usage_key: the UsageKeyV2 subclass used for this learning context
86+
"""

openedx/core/djangoapps/xblock/runtime/learning_core_runtime.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ def save_block(self, block):
317317
# Signal that we've modified this block
318318
learning_context = get_learning_context_impl(usage_key)
319319
learning_context.send_block_updated_event(usage_key)
320+
learning_context.send_container_updated_events(usage_key)
320321

321322
def _get_component_from_usage_key(self, usage_key):
322323
"""

0 commit comments

Comments
 (0)