Skip to content

Commit 1d9ca33

Browse files
ormsbeekdmccormick
andauthored
fix: correct upstream field for migrated libraries (#37804)
This is to fix an issue in the following common migration situation: 1. An existing course references content in a legacy content library. 2. The legacy content library is migrated to the new library system. 3. The user clicks on "Update reference" from the Randomized Content Block in the course. This action is supposed to update the children of the LibraryContentBlock (usually ProblemBlocks) so that the "upstream" attribute is set to point at the UsageKeys of the content in the new libraries they were migrated to. What was happening instead was that the upstream entries for these child blocks were left blank, breaking the upstream/sync connection and making it so that the courses did not receive any updates from the migrated libraries. There were two issues: 1. get_forwarding_for_blocks() was being called with the child UsageKeys in the course, when it should have been called with the v1 library usage keys instead (since those are the things being forwarded). 2. We were checking that the target_key was a v2 Library key, but really the upstream target_key is supposed to be a LibraryUsageLocatorV2, i.e. the key of the specific piece of content, not the library it ended up in. Note on testing: Although there were unit tests for the migration of legacy content libraries, there were not any unit tests for the migration of legacy library *blocks*. This commit adds a minimal test, which would have caught the bug we're fixing. It would be good to add more comprehensive testing unit testing for this part of the migration flow. --------- Co-authored-by: Kyle McCormick <[email protected]>
1 parent 7aaf511 commit 1d9ca33

File tree

2 files changed

+53
-14
lines changed

2 files changed

+53
-14
lines changed

xmodule/library_content_block.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import nh3
1919
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
20-
from opaque_keys.edx.locator import LibraryLocator, LibraryLocatorV2
20+
from opaque_keys.edx.locator import LibraryLocator, LibraryUsageLocatorV2
2121
from web_fragments.fragment import Fragment
2222
from webob import Response
2323
from xblock.core import XBlock
@@ -324,11 +324,15 @@ def v2_update_children_upstream_version(self, user_id=None):
324324
store = modulestore()
325325
with store.bulk_operations(self.course_id):
326326
children = self.get_children()
327-
child_migrations = migrator_api.get_forwarding_for_blocks([child.usage_key for child in children])
328-
for child in children:
329-
old_upstream_key, _ = self.runtime.modulestore.get_block_original_usage(child.usage_key)
327+
# These are the v1 library item upstream UsageKeys
328+
child_old_upstream_keys = [
329+
self.runtime.modulestore.get_block_original_usage(child.usage_key)[0]
330+
for child in children
331+
]
332+
child_migrations = migrator_api.get_forwarding_for_blocks(child_old_upstream_keys)
333+
for child, old_upstream_key in zip(children, child_old_upstream_keys):
330334
upstream_migration = child_migrations.get(old_upstream_key)
331-
if upstream_migration and isinstance(upstream_migration.target_key, LibraryLocatorV2):
335+
if upstream_migration and isinstance(upstream_migration.target_key, LibraryUsageLocatorV2):
332336
child.upstream = str(upstream_migration.target_key)
333337
if upstream_migration.target_version_num:
334338
child.upstream_version = upstream_migration.target_version_num

xmodule/tests/test_library_content.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -736,9 +736,9 @@ def test_removed_invalid(self):
736736
)
737737
@patch('xmodule.html_block.HtmlBlock.author_view', dummy_render, create=True)
738738
@patch('xmodule.x_module.ModuleStoreRuntime.applicable_aside_types', lambda self, block: [])
739-
class TestMigratedLibraryContentRender(LegacyLibraryContentTest):
739+
class TestLegacyLibraryContentBlockMigration(LegacyLibraryContentTest):
740740
"""
741-
Rendering unit tests for LegacyLibraryContentBlock
741+
Unit tests for LegacyLibraryContentBlock
742742
"""
743743

744744
def setUp(self):
@@ -747,16 +747,14 @@ def setUp(self):
747747
super().setUp()
748748
user = UserFactory()
749749
self._sync_lc_block_from_library()
750-
self.organization = OrganizationFactory()
751-
self.lib_key_v2 = LibraryLocatorV2.from_string(
752-
f"lib:{self.organization.short_name}:test-key"
753-
)
750+
self.organization = OrganizationFactory(short_name="myorg")
751+
self.lib_key_v2 = LibraryLocatorV2.from_string("lib:myorg:mylib")
754752
lib_api.create_library(
755753
org=self.organization,
756-
slug=self.lib_key_v2.slug,
757-
title="Test Library",
754+
slug="mylib",
755+
title="My Test V2 Library",
758756
)
759-
self.library_v2 = lib_api.ContentLibrary.objects.get(slug=self.lib_key_v2.slug)
757+
self.library_v2 = lib_api.ContentLibrary.objects.get(slug="mylib")
760758
api.start_migration_to_library(
761759
user=user,
762760
source_key=self.library.location.library_key,
@@ -770,6 +768,43 @@ def setUp(self):
770768
# Migrate block
771769
self.lc_block.upgrade_to_v2_library(None, None)
772770

771+
def test_migration_of_fields(self):
772+
"""
773+
Test that the LC block migration correctly updates the metadata of the LC block and its children.
774+
775+
This tests only the simplest state: The source lib has been migrated with forwarding, exactly once,
776+
and the LC block has also been migrated.
777+
778+
TODO(https://github.com/openedx/edx-platform/issues/37837):
779+
It would be good to also test more cases, including:
780+
* When migration occurs which is non-forwarding, it does *not* affect the childen of this block.
781+
* When the library migration HAS happend but the LC block migration HASN'T YET, then the fields of
782+
the block and its children will be unchanged, but the user will be prompted to upgrade.
783+
* When some or all of the blocks already exist in the target library before the migration, then
784+
the migration target versions will NOT all be 1, and the upstream_versions should reflect that.
785+
* When the target library blocks have been edited and published AFTER the legacy library migration
786+
but BEFORE the LC block migration, then executing the LC block migration will set upstream_version
787+
based on the migration target versions, NOT the latest versions.
788+
"""
789+
assert self.lc_block.is_migrated_to_v2 is True
790+
children = self.lc_block.get_children()
791+
assert len(children) == len(self.lib_blocks)
792+
# The children's legacy library blocks have been migrated to a V2 library.
793+
# We expect that each child's `upstream` has been updated to point at
794+
# the target of each library block's migration.
795+
assert children[0].upstream == "lb:myorg:mylib:html:html_1"
796+
assert children[1].upstream == "lb:myorg:mylib:html:html_2"
797+
assert children[2].upstream == "lb:myorg:mylib:html:html_3"
798+
assert children[3].upstream == "lb:myorg:mylib:html:html_4"
799+
# We also expect that each child's `upstream_version` has been set to the
800+
# version of the migrated library block at the time of its migration, which
801+
# we are assuming is `1` (i.e., the first version, as the blocks did not
802+
# previously exist in the target library).
803+
assert children[0].upstream_version == 1
804+
assert children[1].upstream_version == 1
805+
assert children[2].upstream_version == 1
806+
assert children[3].upstream_version == 1
807+
773808
def test_preview_view(self):
774809
""" Test preview view rendering """
775810
assert len(self.lc_block.children) == len(self.lib_blocks)

0 commit comments

Comments
 (0)