feat: Multiple updates to handle children upstream info [FC-0097] (#37433)
* Which edX user roles will this change impact? "Developer"". * Added `upstream_ready_to_sync_children_info` in `ContainerChildrenSerializer` * Now, the `ContainerChildrenView` can return the `upstream_ready_to_sync_children_info` * Update the child info in `UpstreamLink._check_children_ready_to_sync`
This commit is contained in:
@@ -177,7 +177,22 @@ class ContainerChildrenSerializer(serializers.Serializer):
|
||||
Serializer for representing a vertical container with state and children.
|
||||
"""
|
||||
|
||||
class UpstreamReadyToSyncChildrenInfoSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer used for the `upstream_ready_to_sync_children_info` field
|
||||
"""
|
||||
id = serializers.CharField()
|
||||
name = serializers.CharField()
|
||||
upstream = serializers.CharField()
|
||||
block_type = serializers.CharField()
|
||||
is_modified = serializers.BooleanField()
|
||||
|
||||
children = ContainerChildSerializer(many=True)
|
||||
is_published = serializers.BooleanField()
|
||||
can_paste_component = serializers.BooleanField()
|
||||
display_name = serializers.CharField()
|
||||
upstream_ready_to_sync_children_info = UpstreamReadyToSyncChildrenInfoSerializer(
|
||||
many=True,
|
||||
required=False,
|
||||
help_text="List of dictionaries describing upstream child components readiness to sync."
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ from opaque_keys.edx.keys import CourseKey
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.fields import BooleanField
|
||||
|
||||
from cms.djangoapps.contentstore.config.waffle import CUSTOM_RELATIVE_DATES
|
||||
from cms.djangoapps.contentstore.rest_api.v1.mixins import ContainerHandlerMixin
|
||||
@@ -129,6 +130,11 @@ class ContainerChildrenView(APIView, ContainerHandlerMixin):
|
||||
apidocs.ParameterLocation.PATH,
|
||||
description="Container usage key",
|
||||
),
|
||||
apidocs.string_parameter(
|
||||
"get_upstream_info",
|
||||
apidocs.ParameterLocation.QUERY,
|
||||
description="Gets the info of all ready to sync children",
|
||||
),
|
||||
],
|
||||
responses={
|
||||
200: ContainerChildrenSerializer,
|
||||
@@ -210,6 +216,7 @@ class ContainerChildrenView(APIView, ContainerHandlerMixin):
|
||||
"version_available": 49,
|
||||
"error_message": null,
|
||||
"ready_to_sync": true,
|
||||
"is_ready_to_sync_individually": true,
|
||||
},
|
||||
"actions": {
|
||||
"can_copy": true,
|
||||
@@ -231,11 +238,19 @@ class ContainerChildrenView(APIView, ContainerHandlerMixin):
|
||||
"is_published": false,
|
||||
"can_paste_component": true,
|
||||
"display_name": "Vertical block 1"
|
||||
"upstream_ready_to_sync_children_info": [{
|
||||
"name": "Text",
|
||||
"upstream": "lb:org:mylib:html:abcd",
|
||||
'block_type': "html",
|
||||
'is_modified': true,
|
||||
'id': "block-v1:org+101+101+type@html+block@3e3fa1f88adb4a108cd14e9002143690",
|
||||
}]
|
||||
}
|
||||
```
|
||||
"""
|
||||
usage_key = self.get_object(usage_key_string)
|
||||
current_xblock = get_xblock(usage_key, request.user)
|
||||
get_upstream_info = BooleanField().to_internal_value(request.GET.get("get_upstream_info", False))
|
||||
is_course = current_xblock.scope_ids.usage_id.context_key.is_course
|
||||
|
||||
with modulestore().bulk_operations(usage_key.course_key):
|
||||
@@ -274,10 +289,17 @@ class ContainerChildrenView(APIView, ContainerHandlerMixin):
|
||||
except ItemNotFoundError:
|
||||
logging.error('Could not find any changes for block [%s]', usage_key)
|
||||
|
||||
upstream_ready_to_sync_children_info = []
|
||||
if current_xblock.upstream and get_upstream_info:
|
||||
upstream_link = UpstreamLink.get_for_block(current_xblock)
|
||||
upstream_link_data = upstream_link.to_json(include_child_info=True)
|
||||
upstream_ready_to_sync_children_info = upstream_link_data["ready_to_sync_children"]
|
||||
|
||||
container_data = {
|
||||
"children": children,
|
||||
"is_published": is_published,
|
||||
"can_paste_component": is_course,
|
||||
"upstream_ready_to_sync_children_info": upstream_ready_to_sync_children_info,
|
||||
"display_name": current_xblock.display_name_with_default,
|
||||
}
|
||||
serializer = ContainerChildrenSerializer(container_data)
|
||||
|
||||
@@ -11,6 +11,7 @@ from xblock.validation import ValidationMessage
|
||||
|
||||
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
|
||||
from openedx.core.djangoapps.content_tagging.toggles import DISABLE_TAGGING_FEATURE
|
||||
from openedx.core.djangoapps.content_libraries.tests import ContentLibrariesRestApiTest
|
||||
from xmodule.partitions.partitions import (
|
||||
ENROLLMENT_TRACK_PARTITION_ID,
|
||||
Group,
|
||||
@@ -27,7 +28,7 @@ from xmodule.modulestore import (
|
||||
) # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
class BaseXBlockContainer(CourseTestCase):
|
||||
class BaseXBlockContainer(CourseTestCase, ContentLibrariesRestApiTest):
|
||||
"""
|
||||
Base xBlock container handler.
|
||||
|
||||
@@ -48,6 +49,20 @@ class BaseXBlockContainer(CourseTestCase):
|
||||
This method creates XBlock objects representing a course structure with chapters,
|
||||
sequentials, verticals and others.
|
||||
"""
|
||||
self.lib = self._create_library(
|
||||
slug="containers",
|
||||
title="Container Test Library",
|
||||
description="Units and more",
|
||||
)
|
||||
self.unit = self._create_container(self.lib["id"], "unit", display_name="Unit", slug=None)
|
||||
self.html_block = self._add_block_to_library(self.lib["id"], "html", "Html1", can_stand_alone=False)
|
||||
self._set_library_block_olx(
|
||||
self.html_block["id"],
|
||||
'<html display_name="Html1">updated content upstream 1</html>'
|
||||
)
|
||||
# Set version of html to 2
|
||||
self._publish_library_block(self.html_block["id"])
|
||||
|
||||
self.chapter = self.create_block(
|
||||
parent=self.course.location,
|
||||
category="chapter",
|
||||
@@ -60,7 +75,13 @@ class BaseXBlockContainer(CourseTestCase):
|
||||
display_name="Lesson 1",
|
||||
)
|
||||
|
||||
self.vertical = self.create_block(self.sequential.location, "vertical", "Unit")
|
||||
self.vertical = self.create_block(
|
||||
self.sequential.location,
|
||||
"vertical",
|
||||
"Unit",
|
||||
upstream=self.unit["id"],
|
||||
upstream_version=1,
|
||||
)
|
||||
|
||||
self.html_unit_first = self.create_block(
|
||||
parent=self.vertical.location,
|
||||
@@ -72,8 +93,8 @@ class BaseXBlockContainer(CourseTestCase):
|
||||
parent=self.vertical.location,
|
||||
category="html",
|
||||
display_name="Html Content 2",
|
||||
upstream="lb:FakeOrg:FakeLib:html:FakeBlock",
|
||||
upstream_version=5,
|
||||
upstream=self.html_block["id"],
|
||||
upstream_version=1,
|
||||
)
|
||||
|
||||
def create_block(self, parent, category, display_name, **kwargs):
|
||||
@@ -209,6 +230,27 @@ class ContainerVerticalViewTest(BaseXBlockContainer):
|
||||
self.assertFalse(data["is_published"])
|
||||
self.assertTrue(data["can_paste_component"])
|
||||
self.assertEqual(data["display_name"], "Unit")
|
||||
self.assertEqual(data["upstream_ready_to_sync_children_info"], [])
|
||||
|
||||
def test_success_response_with_upstream_info(self):
|
||||
"""
|
||||
Check that endpoint returns valid response data using `get_upstream_info` query param
|
||||
"""
|
||||
url = self.get_reverse_url(self.vertical.location)
|
||||
response = self.client.get(f"{url}?get_upstream_info=true")
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
data = response.json()
|
||||
self.assertEqual(len(data["children"]), 2)
|
||||
self.assertFalse(data["is_published"])
|
||||
self.assertTrue(data["can_paste_component"])
|
||||
self.assertEqual(data["display_name"], "Unit")
|
||||
self.assertEqual(data["upstream_ready_to_sync_children_info"], [{
|
||||
"id": str(self.html_unit_second.usage_key),
|
||||
"upstream": self.html_block["id"],
|
||||
"block_type": "html",
|
||||
"is_modified": False,
|
||||
"name": "Html Content 2",
|
||||
}])
|
||||
|
||||
def test_xblock_is_published(self):
|
||||
"""
|
||||
@@ -275,12 +317,12 @@ class ContainerVerticalViewTest(BaseXBlockContainer):
|
||||
"can_manage_tags": True,
|
||||
},
|
||||
"upstream_link": {
|
||||
"upstream_ref": "lb:FakeOrg:FakeLib:html:FakeBlock",
|
||||
"version_synced": 5,
|
||||
"version_available": None,
|
||||
"upstream_ref": self.html_block["id"],
|
||||
"version_synced": 1,
|
||||
"version_available": 2,
|
||||
"version_declined": None,
|
||||
"error_message": "Linked upstream library block was not found in the system",
|
||||
"ready_to_sync": False,
|
||||
"error_message": None,
|
||||
"ready_to_sync": True,
|
||||
"has_top_level_parent": False,
|
||||
"is_modified": False,
|
||||
},
|
||||
|
||||
@@ -643,6 +643,8 @@ class GetUpstreamViewTest(
|
||||
self.assertDictEqual(data['ready_to_sync_children'][0], {
|
||||
'name': html_block.display_name,
|
||||
'upstream': str(self.html_lib_id_2),
|
||||
'block_type': 'html',
|
||||
'is_modified': False,
|
||||
'id': str(html_block.usage_key),
|
||||
})
|
||||
|
||||
|
||||
@@ -121,6 +121,8 @@ class UpstreamLink:
|
||||
child_info.append({
|
||||
'name': child.display_name,
|
||||
'upstream': getattr(child, 'upstream', None),
|
||||
'block_type': child.usage_key.block_type,
|
||||
'is_modified': child_upstream_link.is_modified,
|
||||
'id': str(child.usage_key),
|
||||
})
|
||||
if return_fast:
|
||||
@@ -180,6 +182,7 @@ class UpstreamLink:
|
||||
**asdict(self),
|
||||
"ready_to_sync": self.ready_to_sync,
|
||||
"upstream_link": self.upstream_link,
|
||||
"is_ready_to_sync_individually": self.is_ready_to_sync_individually,
|
||||
}
|
||||
if (
|
||||
include_child_info
|
||||
|
||||
Reference in New Issue
Block a user