Skip to content

Commit

Permalink
feat: display error notifications on the Unit page (#34450)
Browse files Browse the repository at this point in the history
  • Loading branch information
ruzniaievdm authored Apr 9, 2024
1 parent 3c7e162 commit a8586e5
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@
)


class MessageValidation(serializers.Serializer):
"""
Serializer for representing XBlock error.
"""

text = serializers.CharField()
type = serializers.CharField()


class ChildAncestorSerializer(serializers.Serializer):
"""
Serializer for representing child blocks in the ancestor XBlock.
Expand Down Expand Up @@ -105,6 +114,8 @@ class ChildVerticalContainerSerializer(serializers.Serializer):
user_partition_info = serializers.DictField()
user_partitions = serializers.ListField()
actions = serializers.SerializerMethodField()
validation_messages = MessageValidation(many=True)
render_error = serializers.CharField()

def get_actions(self, obj): # pylint: disable=unused-argument
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
"""
Unit tests for the vertical block.
"""

from django.urls import reverse
from rest_framework import status
from edx_toggles.toggles.testutils import override_waffle_flag
from xblock.validation import ValidationMessage

from cms.djangoapps.contentstore.tests.utils import CourseTestCase
from cms.djangoapps.contentstore.toggles import ENABLE_TAGGING_TAXONOMY_LIST_PAGE
from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID
from xmodule.partitions.partitions import (
ENROLLMENT_TRACK_PARTITION_ID,
Group,
UserPartition,
)
from xmodule.modulestore.django import (
modulestore,
) # lint-amnesty, pylint: disable=wrong-import-order
Expand Down Expand Up @@ -96,6 +102,13 @@ def publish_item(self, store, item_location):
with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred):
store.publish(item_location, ModuleStoreEnum.UserID.test)

def set_group_access(self, xblock, value):
"""
Sets group_access to specified value and calls update_item to persist the change.
"""
xblock.group_access = value
self.store.update_item(xblock, self.user.id)


class ContainerHandlerViewTest(BaseXBlockContainer):
"""
Expand Down Expand Up @@ -161,7 +174,7 @@ def test_children_content(self):
expected_user_partition_info = {
"selectable_partitions": [],
"selected_partition_index": -1,
"selected_groups_label": ""
"selected_groups_label": "",
}

expected_user_partitions = [
Expand All @@ -170,13 +183,8 @@ def test_children_content(self):
"name": "Enrollment Track Groups",
"scheme": "enrollment_track",
"groups": [
{
"id": 1,
"name": "Audit",
"selected": False,
"deleted": False
}
]
{"id": 1, "name": "Audit", "selected": False, "deleted": False}
],
}
]

Expand All @@ -190,16 +198,20 @@ def test_children_content(self):
"actions": {
"can_manage_tags": True,
},
"validation_messages": [],
"render_error": "",
},
{
"name": self.html_unit_second.display_name_with_default,
"block_id": str(self.html_unit_second.location),
"block_type": self.html_unit_second.location.block_type,
"user_partition_info": expected_user_partition_info,
"user_partitions": expected_user_partitions,
"actions": {
"can_manage_tags": True,
},
"user_partition_info": expected_user_partition_info,
"user_partitions": expected_user_partitions,
"validation_messages": [],
"render_error": "",
},
]
self.assertEqual(response.data["children"], expected_response)
Expand All @@ -224,3 +236,42 @@ def test_actions_with_turned_off_taxonomy_flag(self):
response = self.client.get(url)
for children in response.data["children"]:
self.assertFalse(children["actions"]["can_manage_tags"])

def test_validation_errors(self):
"""
Check that child has an error.
"""
self.course.user_partitions = [
UserPartition(
0,
"first_partition",
"Test Partition",
[Group("0", "alpha"), Group("1", "beta")],
),
]
self.store.update_item(self.course, self.user.id)

user_partition = self.course.user_partitions[0]
vertical = self.store.get_item(self.vertical.location)
html_unit_first = self.store.get_item(self.html_unit_first.location)

group_first = user_partition.groups[0]
group_second = user_partition.groups[1]

# Set access settings so html will contradict vertical
self.set_group_access(vertical, {user_partition.id: [group_second.id]})
self.set_group_access(html_unit_first, {user_partition.id: [group_first.id]})

# update vertical/html
vertical = self.store.get_item(self.vertical.location)
html_unit_first = self.store.get_item(self.html_unit_first.location)

url = self.get_reverse_url(self.vertical.location)
response = self.client.get(url)
children_response = response.data["children"]

# Verify that html_unit_first access settings contradict its parent's access settings.
self.assertEqual(children_response[0]["validation_messages"][0]["type"], ValidationMessage.ERROR)

# Verify that html_unit_second has no validation messages.
self.assertFalse(children_response[1]["validation_messages"])
22 changes: 20 additions & 2 deletions cms/djangoapps/contentstore/rest_api/v1/views/vertical_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
get_container_handler_context,
get_user_partition_info,
get_visibility_partition_info,
get_xblock_validation_messages,
get_xblock_render_error,
)
from cms.djangoapps.contentstore.views.component import _get_item_in_course
from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import get_xblock
Expand Down Expand Up @@ -194,7 +196,9 @@ def get(self, request: Request, usage_key_string: str):
"user_partitions": {}
"actions": {
"can_manage_tags": true,
}
},
"has_validation_error": false,
"validation_errors": [],
},
{
"name": "Video",
Expand All @@ -205,6 +209,8 @@ def get(self, request: Request, usage_key_string: str):
"actions": {
"can_manage_tags": true,
}
"validation_messages": [],
"render_error": "",
},
{
"name": "Text",
Expand All @@ -214,7 +220,14 @@ def get(self, request: Request, usage_key_string: str):
"user_partitions": {},
"actions": {
"can_manage_tags": true,
}
},
"validation_messages": [
{
"text": "This component's access settings contradict its parent's access settings.",
"type": "error"
}
],
"render_error": "Unterminated control keyword: 'if' in file '../problem.html'",
},
],
"is_published": false
Expand All @@ -232,12 +245,17 @@ def get(self, request: Request, usage_key_string: str):
child_info = modulestore().get_item(child)
user_partition_info = get_visibility_partition_info(child_info, course=course)
user_partitions = get_user_partition_info(child_info, course=course)
validation_messages = get_xblock_validation_messages(child_info)
render_error = get_xblock_render_error(request, child_info)

children.append({
"name": child_info.display_name_with_default,
"block_id": child_info.location,
"block_type": child_info.location.block_type,
"user_partition_info": user_partition_info,
"user_partitions": user_partitions,
"validation_messages": validation_messages,
"render_error": render_error,
})

is_published = not modulestore().has_changes(current_xblock)
Expand Down
57 changes: 57 additions & 0 deletions cms/djangoapps/contentstore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2234,3 +2234,60 @@ def send_course_update_notification(course_key, content, user):
audience_filters={},
)
COURSE_NOTIFICATION_REQUESTED.send_event(course_notification_data=notification_data)


def get_xblock_validation_messages(xblock):
"""
Retrieves validation messages for a given xblock.
Args:
xblock: The xblock object to validate.
Returns:
list: A list of validation error messages.
"""
validation_json = xblock.validate().to_json()
return validation_json['messages']


def get_xblock_render_error(request, xblock):
"""
Checks if there are any rendering errors for a given block and return these.
Args:
request: WSGI request object
xblock: The xblock object to rendering.
Returns:
str: Error message which happened while rendering of xblock.
"""
from cms.djangoapps.contentstore.views.preview import _load_preview_block
from xmodule.studio_editable import has_author_view
from xmodule.x_module import AUTHOR_VIEW, STUDENT_VIEW

def get_xblock_render_context(request, block):
"""
Return a dict of the data needs for render of each block.
"""
can_edit = has_studio_write_access(request.user, block.usage_key.course_key)

return {
"is_unit_page": False,
"can_edit": can_edit,
"root_xblock": xblock,
"reorderable_items": set(),
"paging": None,
"force_render": None,
"item_url": "/container/{block.location}",
"tags_count_map": {},
}

try:
block = _load_preview_block(request, xblock)
preview_view = AUTHOR_VIEW if has_author_view(block) else STUDENT_VIEW
render_context = get_xblock_render_context(request, block)
block.render(preview_view, render_context)
except Exception as exc: # pylint: disable=broad-except
return str(exc)

return ""

0 comments on commit a8586e5

Please sign in to comment.