-
Notifications
You must be signed in to change notification settings - Fork 4.2k
fix: Various Edge-Case Bugs & Data Issues in modulestore_migrator #37711
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
885d3b0 to
5aa4695
Compare
8a0656c to
20f0c30
Compare
fa5a416 to
765e18a
Compare
|
Sandbox deployment successful 🚀 |
765e18a to
00cb47e
Compare
|
Sandbox deployment successful 🚀 |
|
Sandbox deployment successful 🚀 |
|
Sandbox deployment successful 🚀 |
|
Sandbox deployment successful 🚀 |
75dda80 to
d8f04a1
Compare
|
Sandbox deployment successful 🚀 |
|
Sandbox deployment successful 🚀 |
|
Sandbox deployment successful 🚀 |
|
Sandbox deployment successful 🚀 |
|
Sandbox deployment successful 🚀 |
|
Sandbox deployment successful 🚀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 Context and rationale, for reviewers ⬇️
| library = lib_api.ContentLibrary.objects.get(slug=self.lib_key_v2.slug) | ||
| learning_package = library.learning_package | ||
| # Create a migration source for the legacy library | ||
| self.source = ModulestoreSourceFactory(key=self.lib_key_1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not necessary, as the migrator_api creates missing Source objects automatically
| forward_source_to_target=True, | ||
| ) | ||
| migrator_api.start_migration_to_library( | ||
| user=self.user, | ||
| source_key=self.lib_key_2, | ||
| target_library_key=self.lib_key_v2, | ||
| target_collection_slug=collection_key, | ||
| composition_level=CompositionLevel.Component.value, | ||
| repeat_handling_strategy=RepeatHandlingStrategy.Skip.value, | ||
| preserve_url_slugs=True, | ||
| forward_source_to_target=False, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now testing both forward_source_to_target=True and forward_source_to_target=False
| def get_library_context(request, request_is_json=False): | ||
| """ | ||
| Utils is used to get context of course home library tab. | ||
| It is used for both DRF and django views. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated this comment, as the django-based legacy libraries listing is gone.
| user_can_create_library, | ||
| ) | ||
|
|
||
| is_migrated: bool | None # None means: do not filter on is_migrated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No functional change to this view handler for any of the 2xx happy-path cases.
The changes here just make the handler more careful with types and parsing, so that some client errors which would have resulted in 500s are now caught here and returned as 400s with validation messages.
| assert '<li>html 3</li>' in rendered.content | ||
| assert '<li>html 4</li>' in rendered.content | ||
|
|
||
| def test_xml_export_import_cycle(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's another test_xml_export_import_cycle test method in this module, which sufficiently tests the OLX round trip.
The extra assertions that this test has, in particular child.xml_attributes.get('upstream') is not None, do not actually test anything worthwhile. The value of upstream on these children is "", which is not None, but also, it may as well be None.
Seeing that this test method is part of TestLibraryContentRender, I believe that this test method was added back when the rendering of the legacy library content block would trigger the migration. This is no longer the case, but the test never failed when we switched to user-triggered migration(oops). Anyway, it's obsolete, so we remove it.
| def get_tools(self, to_read_library_content: bool = False) -> 'LegacyLibraryToolsService': | ||
| def get_tools(self, to_read_library_content: bool = False) -> LegacyLibraryToolsService: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is unrelated, but it was always showing up in Pylance and driving me crazy
xmodule/library_content_block.py
Outdated
| and is_successfully_migrated(self.source_library_key, source_version=self.source_library_version) | ||
| and forward_legacy_library(self.source_library_key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before:
- the LCB reference is ready to be updated if there's a successful migration of the library, started at it its current version.
Now:
- the LCB reference is ready to be updated if there's a successful forwarding-eanbled (i.e. authoritative) migration of the library, regardless of the source library's current version.
xmodule/library_content_block.py
Outdated
| # appears when it is published | ||
| child.upstream_version = 0 | ||
| children = self.get_children() | ||
| child_migrations = migrator_api.forward_blocks([child.usage_key for child in children]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We now forward using mappings from the forwarded migration rather than any old mappings.
Co-authored-by: David Ormsbee <dave@axim.org>
this makes it so the UI-based migration generates nice slugs
Co-authored-by: David Ormsbee <dave@axim.org>
3193a6f to
64da92e
Compare
| self.source_library_id | ||
| and self.source_library_version | ||
| and is_successfully_migrated(self.source_library_key, source_version=self.source_library_version) | ||
| and is_forwarded(self.source_library_key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bradenmacdonald sorry, I lost track of your comment about keeping an is_successfully_migrated API function in order to make the API more obvious. i think that's a good idea.
I'm trying to keep "forwarded" and "migrated" distinct (the former being the authoritative thing), so I've gone with is_forwarded.
I'm planning to merge later tonight. Let me know if you'd like to see anything different (or I can follow up with a fix on master).
|
@ormsbee Looks like the ulmo cherry-pick has some conflicts. I'll resolve, backport, and test tomorrow AM |
For legacy library_content references in courses, this PR: - **Removes the spurious sync after updating a reference to a migrated library**, so that users don't need to "update" their content _after_ updating their reference, _unless_ there were real content edits that happened since they last synced. We do this by correctly associating a DraftChangeLogRecord with the ModulestoreBlockSource migration artifact, and then comparing that version information before offering a sync. (related issue: openedx/frontend-app-authoring#2626). - **Prompts users to update a reference to a migrated library with higher priority than prompting them to sync legacy content updates for that reference**, so that users don't end up needing to accept legacy content updates in order to get a to a point where they can update to V2 content. - **Ensures the library references in courses always follow the correct migration,** as defined by the data `forwarded` fields in the data model, which are populated based on the REST API spec and the stated product UI requirements. For the migration itself, this PR: - **Allows non-admins to migrate libraries**, fixing: openedx#37774 - **When triggered via the UI, ensures the migration uses nice title-based target slugs instead of ugly source-hash-based slugs.** We've had this as an option for a long time, but preserve_url_slugs defaulted to True instead of False in the REST API serializer, so we weren't taking advantage of it. - **Unifies logic between single-source and bulk migration**. These were implement as two separate code paths, with drift in their implementations. In particular, the collection update-vs-create-new logic was completely different for single-souce vs. bulk. - **When using the Skip or Update strategies for repeats, it consistently follows mappings established by the latest successful migration** rather than following mappings across arbitrary previous migrations. - **We log unexpected exceptions more often**, although there is so much more room for improvement here. - **Adds more validation to the REST API** so that client mistakes more often become 400s with validation messages rather than 500s. For developers, this PR: - Adds unit tests to the REST API - Ensures that all migration business logic now goes through a general-purpose Python API. - Ensures that the data model (specifically `forwarded`, and `change_log_record`) is now populated and respected. - Adds more type annotations.
For legacy library_content references in courses, this PR: - **Removes the spurious sync after updating a reference to a migrated library**, so that users don't need to "update" their content _after_ updating their reference, _unless_ there were real content edits that happened since they last synced. We do this by correctly associating a DraftChangeLogRecord with the ModulestoreBlockSource migration artifact, and then comparing that version information before offering a sync. (related issue: openedx/frontend-app-authoring#2626). - **Prompts users to update a reference to a migrated library with higher priority than prompting them to sync legacy content updates for that reference**, so that users don't end up needing to accept legacy content updates in order to get a to a point where they can update to V2 content. - **Ensures the library references in courses always follow the correct migration,** as defined by the data `forwarded` fields in the data model, which are populated based on the REST API spec and the stated product UI requirements. For the migration itself, this PR: - **Allows non-admins to migrate libraries**, fixing: openedx#37774 - **When triggered via the UI, ensures the migration uses nice title-based target slugs instead of ugly source-hash-based slugs.** We've had this as an option for a long time, but preserve_url_slugs defaulted to True instead of False in the REST API serializer, so we weren't taking advantage of it. - **Unifies logic between single-source and bulk migration**. These were implement as two separate code paths, with drift in their implementations. In particular, the collection update-vs-create-new logic was completely different for single-souce vs. bulk. - **When using the Skip or Update strategies for repeats, it consistently follows mappings established by the latest successful migration** rather than following mappings across arbitrary previous migrations. - **We log unexpected exceptions more often**, although there is so much more room for improvement here. - **Adds more validation to the REST API** so that client mistakes more often become 400s with validation messages rather than 500s. For developers, this PR: - Adds unit tests to the REST API - Ensures that all migration business logic now goes through a general-purpose Python API. - Ensures that the data model (specifically `forwarded`, and `change_log_record`) is now populated and respected. - Adds more type annotations.
For legacy library_content references in courses, this PR: - **Removes the spurious sync after updating a reference to a migrated library**, so that users don't need to "update" their content _after_ updating their reference, _unless_ there were real content edits that happened since they last synced. We do this by correctly associating a DraftChangeLogRecord with the ModulestoreBlockSource migration artifact, and then comparing that version information before offering a sync. (related issue: openedx/frontend-app-authoring#2626). - **Prompts users to update a reference to a migrated library with higher priority than prompting them to sync legacy content updates for that reference**, so that users don't end up needing to accept legacy content updates in order to get a to a point where they can update to V2 content. - **Ensures the library references in courses always follow the correct migration,** as defined by the data `forwarded` fields in the data model, which are populated based on the REST API spec and the stated product UI requirements. For the migration itself, this PR: - **Allows non-admins to migrate libraries**, fixing: openedx#37774 - **When triggered via the UI, ensures the migration uses nice title-based target slugs instead of ugly source-hash-based slugs.** We've had this as an option for a long time, but preserve_url_slugs defaulted to True instead of False in the REST API serializer, so we weren't taking advantage of it. - **Unifies logic between single-source and bulk migration**. These were implement as two separate code paths, with drift in their implementations. In particular, the collection update-vs-create-new logic was completely different for single-souce vs. bulk. - **When using the Skip or Update strategies for repeats, it consistently follows mappings established by the latest successful migration** rather than following mappings across arbitrary previous migrations. - **We log unexpected exceptions more often**, although there is so much more room for improvement here. - **Adds more validation to the REST API** so that client mistakes more often become 400s with validation messages rather than 500s. For developers, this PR: - Adds unit tests to the REST API - Ensures that all migration business logic now goes through a general-purpose Python API. - Ensures that the data model (specifically `forwarded`, and `change_log_record`) is now populated and respected. - Adds more type annotations. Backports: 91e521e
For legacy library_content references in courses, this PR: - **Removes the spurious sync after updating a reference to a migrated library**, so that users don't need to "update" their content _after_ updating their reference, _unless_ there were real content edits that happened since they last synced. We do this by correctly associating a DraftChangeLogRecord with the ModulestoreBlockSource migration artifact, and then comparing that version information before offering a sync. (related issue: openedx/frontend-app-authoring#2626). - **Prompts users to update a reference to a migrated library with higher priority than prompting them to sync legacy content updates for that reference**, so that users don't end up needing to accept legacy content updates in order to get a to a point where they can update to V2 content. - **Ensures the library references in courses always follow the correct migration,** as defined by the data `forwarded` fields in the data model, which are populated based on the REST API spec and the stated product UI requirements. * For the migration itself, this PR: - **Allows non-admins to migrate libraries**, fixing: openedx#37774 - **When triggered via the UI, ensures the migration uses nice title-based target slugs instead of ugly source-hash-based slugs.** We've had this as an option for a long time, but preserve_url_slugs defaulted to True instead of False in the REST API serializer, so we weren't taking advantage of it. - **Unifies logic between single-source and bulk migration**. These were implement as two separate code paths, with drift in their implementations. In particular, the collection update-vs-create-new logic was completely different for single-souce vs. bulk. - **When using the Skip or Update strategies for repeats, it consistently follows mappings established by the latest successful migration** rather than following mappings across arbitrary previous migrations. - **We log unexpected exceptions more often**, although there is so much more room for improvement here. - **Adds more validation to the REST API** so that client mistakes more often become 400s with validation messages rather than 500s. For developers, this PR: - Adds unit tests to the REST API - Ensures that all migration business logic now goes through a general-purpose Python API. - Ensures that the data model (specifically `forwarded`, and `change_log_record`) is now populated and respected. - Adds more type annotations.
Description & Supporting Info
Reviewers: I've left GH comments throughout all changed files that will hopefully make this easier to digest.
This PR simultaneously addresses several issues in the modulestore_migrator, which may not be perceptible to the average Studio user on the happy-path, but will present themselves to:
What exactly it fixes
For legacy library_content references in courses, this PR:
forwardedfields in the data model, which are populated based on the REST API spec and the stated product UI requirements.For the migration itself, this PR:
For developers, this PR:
forwarded, andchange_log_record) is now populated and respected.Testing instructions
I tested the migration UI with a variety of libraries. I also did some light testing of the Django admin workflow.
The REST API could use more manual testing, but I'm prioritizing merging this first so that we can get a backport PR up and test against Ulmo.
Other information
AI Coding notes
I used Claude Code to generate cms/djangoapps/modulestore_migrator/tests/test_rest_api.py. Here was the original prompt:
I reviewed every test manually, and went through several rounds of revision to address instances where it mocked too heavily, misunderstood the data model, lacked context (e.g. django-user-tasks), or made stylistic choices that I didn't like (e.g.
self.assertIn(x, y)vsassert x in y). Here's two examples of how I had it revise the code:Claude also found a bug in the REST API, which I fixed:
Deadline
ASAP, needs Ulmo backport