Skip to content

Commit

Permalink
feat: [AXIMST-496] error notification when xblock restriction conflic…
Browse files Browse the repository at this point in the history
…ts (#2505)
  • Loading branch information
ruzniaievdm authored Feb 14, 2024
1 parent 7573b25 commit d1bff77
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 24 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):
actions = serializers.SerializerMethodField()
user_partition_info = serializers.DictField()
user_partitions = serializers.ListField()
has_validation_error = serializers.BooleanField()
validation_errors = MessageValidation(many=True)

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 @@ -194,7 +202,9 @@ def test_children_content(self):
"can_manage_tags": True,
},
"user_partition_info": expected_user_partition_info,
"user_partitions": expected_user_partitions
"user_partitions": expected_user_partitions,
"has_validation_error": False,
"validation_errors": [],
},
{
"name": self.html_unit_second.display_name_with_default,
Expand All @@ -210,6 +220,8 @@ def test_children_content(self):
},
"user_partition_info": expected_user_partition_info,
"user_partitions": expected_user_partitions,
"has_validation_error": False,
"validation_errors": [],
},
]
self.assertEqual(response.data["children"], expected_response)
Expand All @@ -234,3 +246,47 @@ 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"]

# Check for an error in html_unit_first xblock
self.assertTrue(children_response[0]["has_validation_error"])

# Verify that html access settings contradict its parent's access settings.
validation = html_unit_first.validate()
self.assertEqual(len(validation.messages), 1)
self.assertEqual(validation.to_json()['messages'][0]['type'], ValidationMessage.ERROR)

# Check for an error in html_unit_second xblock
self.assertFalse(children_response[1]["has_validation_error"])
29 changes: 15 additions & 14 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,7 @@
get_container_handler_context,
get_user_partition_info,
get_visibility_partition_info,
get_validation_messages,
)
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 @@ -186,19 +187,6 @@ def get(self, request: Request, usage_key_string: str):
```json
{
"children": [
{
"name": "Drag and Drop",
"block_id": "block-v1:org+101+101+type@drag-and-drop-v2+block@7599275ace6b46f5a482078a2954ca16",
"block_type": "drag-and-drop-v2",
"actions": {
"can_copy": true,
"can_duplicate": true,
"can_move": true,
"can_manage_access": true,
"can_delete": true,
"can_manage_tags": true,
}
},
{
"name": "Video",
"block_id": "block-v1:org+101+101+type@video+block@0e3d39b12d7c4345981bda6b3511a9bf",
Expand All @@ -211,6 +199,8 @@ def get(self, request: Request, usage_key_string: str):
"can_delete": true,
"can_manage_tags": true,
}
"has_validation_error": false,
"validation_errors": [],
},
{
"name": "Text",
Expand All @@ -223,7 +213,14 @@ def get(self, request: Request, usage_key_string: str):
"can_manage_access": true,
"can_delete": true,
"can_manage_tags": true,
}
},
"has_validation_error": true,
"validation_errors": [
{
"text": "This component's access settings contradict its parent's access settings.",
"type": "error"
}
]
},
],
"is_published": false,
Expand All @@ -242,12 +239,16 @@ 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_errors, has_validation_error = get_validation_messages(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,
"has_validation_error": has_validation_error,
"validation_errors": validation_errors,
})

is_published = not modulestore().has_changes(current_xblock)
Expand Down
18 changes: 18 additions & 0 deletions cms/djangoapps/contentstore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2139,3 +2139,21 @@ def track_course_update_event(course_key, user, event_data=None):
context = contexts.course_context_from_course_id(course_key)
with tracker.get_tracker().context(event_name, context):
tracker.emit(event_name, event_data)


def get_validation_messages(xblock):
"""
Retrieves validation messages for a given xblock.
Args:
xblock: The xblock object to validate.
Returns:
tuple:
- validation_errors (list): A list of validation error messages.
- has_validation_error (bool): True if there are validation errors, False otherwise.
"""
validation_json = xblock.validate().to_json()
validation_errors = validation_json['messages']
has_validation_error = bool(validation_errors)
return validation_errors, has_validation_error

0 comments on commit d1bff77

Please sign in to comment.