-
Notifications
You must be signed in to change notification settings - Fork 4.3k
feat: Preview migration api [FC-0114] #37818
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
Changes from 8 commits
26104e3
4701f55
262671f
0cfb1e8
3a86618
69d03ed
5f9b576
c888fd7
946f3d0
7ec0a1d
65d5e01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,18 +5,27 @@ | |
|
|
||
| import typing as t | ||
| from uuid import UUID | ||
| from django.conf import settings | ||
|
|
||
| from opaque_keys.edx.keys import UsageKey | ||
| from opaque_keys.edx.locator import ( | ||
| LibraryLocatorV2, LibraryUsageLocatorV2, LibraryContainerLocator | ||
| ) | ||
| from openedx_learning.api.authoring import get_draft_version | ||
| from openedx_learning.api.authoring import get_draft_version, get_all_drafts | ||
| from openedx_learning.api.authoring_models import ( | ||
| PublishableEntityVersion, PublishableEntity, DraftChangeLogRecord | ||
| ) | ||
| from xblock.plugin import PluginMissingError | ||
|
|
||
| from openedx.core.djangoapps.content_libraries.api import ( | ||
| library_component_usage_key, library_container_locator | ||
| library_component_usage_key, library_container_locator, | ||
| validate_can_add_block_to_library, BlockLimitReachedError, | ||
| IncompatibleTypesError, LibraryBlockAlreadyExists, | ||
| ContentLibrary | ||
| ) | ||
| from openedx.core.djangoapps.content.search.api import ( | ||
| fetch_block_types, | ||
| get_all_blocks_from_context, | ||
| ) | ||
|
|
||
| from ..data import ( | ||
|
|
@@ -32,6 +41,7 @@ | |
| 'get_forwarding_for_blocks', | ||
| 'get_migrations', | ||
| 'get_migration_blocks', | ||
| 'preview_migration', | ||
| ) | ||
|
|
||
|
|
||
|
|
@@ -242,3 +252,117 @@ def _block_migration_success( | |
| target_title=target_title, | ||
| target_version_num=target_version_num, | ||
| ) | ||
|
|
||
|
|
||
| def preview_migration(source_key: SourceContextKey, target_key: LibraryLocatorV2): | ||
| """ | ||
| Returns a summary preview of the migration given a source key and a target key | ||
| on this form: | ||
|
|
||
| ``` | ||
| { | ||
| "state": "partial", | ||
| "unsupported_blocks": 4, | ||
| "unsupported_percentage": 25, | ||
| "blocks_limit": 1000, | ||
| "total_blocks": 20, | ||
| "total_components": 10, | ||
| "sections": 2, | ||
| "subsections": 3, | ||
| "units": 5, | ||
| } | ||
| ``` | ||
|
|
||
| List of states: | ||
| - 'success': The migration can be carried out in its entirety | ||
| - 'partial': The migration will be partial, because there are unsupported blocks. | ||
| - 'block_limit_reached': The migration cannot be performed because the block limit per library has been reached. | ||
|
|
||
| TODO: For now, the repeat_handling_strategy is not taken into account. This can be taken into | ||
| account for a more advanced summary. | ||
| """ | ||
| # Get all containers and components from the source key | ||
| blocks = get_all_blocks_from_context(str(source_key), ["block_type", "block_id"]) | ||
|
|
||
| unsupported_blocks = [] | ||
| total_blocks = 0 | ||
| total_components = 0 | ||
| sections = 0 | ||
| subsections = 0 | ||
| units = 0 | ||
| blocks_limit = settings.MAX_BLOCKS_PER_CONTENT_LIBRARY | ||
|
|
||
| # Builds the summary: counts every container and verify if each component can be added to the library | ||
| for block in blocks: | ||
| block_type = block["block_type"] | ||
| block_id = block["block_id"] | ||
| total_blocks += 1 | ||
| if block_type not in ['chapter', 'sequential', 'vertical']: | ||
| total_components += 1 | ||
| try: | ||
| validate_can_add_block_to_library( | ||
| target_key, | ||
| block_type, | ||
| block_id, | ||
| ) | ||
| except BlockLimitReachedError: | ||
| return { | ||
| "state": "block_limit_reached", | ||
| "unsupported_blocks": 0, | ||
| "unsupported_percentage": 0, | ||
| "blocks_limit": blocks_limit, | ||
| "total_blocks": 0, | ||
| "total_components": 0, | ||
| "sections": 0, | ||
| "subsections": 0, | ||
| "units": 0, | ||
| } | ||
| except (IncompatibleTypesError, PluginMissingError): | ||
| unsupported_blocks.append(block["usage_key"]) | ||
| except LibraryBlockAlreadyExists: | ||
| # Skip this validation, The block may be repeated in the library, but that's not a bad thing. | ||
| pass | ||
| elif block_type == "chapter": | ||
| sections += 1 | ||
| elif block_type == "sequential": | ||
| subsections += 1 | ||
| elif block_type == "vertical": | ||
| units += 1 | ||
|
|
||
| # Gets the count of children of unsupported blocks | ||
| quoted_keys = ','.join(f'"{key}"' for key in unsupported_blocks) | ||
| unsupportedBlocksChildren = fetch_block_types( | ||
| [ | ||
| f'context_key = "{source_key}"', | ||
| f'breadcrumbs.usage_key IN [{quoted_keys}]' | ||
| ], | ||
| ) | ||
| # Final unsupported blocks count | ||
| # The unsupported children are subtracted from the totals since they have already been counted in the first query. | ||
| unsupported_blocks_count = len(unsupported_blocks) | ||
| total_blocks -= unsupportedBlocksChildren["estimatedTotalHits"] | ||
| total_components -= unsupportedBlocksChildren["estimatedTotalHits"] | ||
| unsupported_percentage = (unsupported_blocks_count / total_blocks) * 100 | ||
|
Comment on lines
+337
to
+348
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ChrisChV As per latest requirements, we need to ignore children counts from the total counts and percentage. See openedx/frontend-app-authoring#2525 (comment) and the related PR: openedx/frontend-app-authoring#2774 |
||
|
|
||
| state = "success" | ||
| if unsupported_blocks_count: | ||
| state = "partial" | ||
|
|
||
| # Checks if this migration reaches the block limit | ||
| content_library = ContentLibrary.objects.get_by_key(target_key) | ||
| assert content_library.learning_package_id is not None | ||
| target_item_counts = get_all_drafts(content_library.learning_package_id).count() | ||
| if (target_item_counts + total_blocks - unsupported_blocks_count) > blocks_limit: | ||
| state = "block_limit_reached" | ||
|
|
||
| return { | ||
| "state": state, | ||
| "unsupported_blocks": unsupported_blocks_count, | ||
| "unsupported_percentage": unsupported_percentage, | ||
| "blocks_limit": blocks_limit, | ||
| "total_blocks": total_blocks, | ||
| "total_components": total_components, | ||
| "sections": sections, | ||
| "subsections": subsections, | ||
| "units": units, | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.