Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions cms/djangoapps/contentstore/core/course_optimizer_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from cms.djangoapps.contentstore.tasks import CourseLinkCheckTask, LinkState
from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import get_xblock
from cms.djangoapps.contentstore.xblock_storage_handlers.xblock_helpers import usage_key_with_run
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore


# Restricts status in the REST API to only those which the requesting user has permission to view.
Expand Down Expand Up @@ -285,3 +287,26 @@ def _create_dto_recursive(xblock_node, xblock_dictionary):
xblock_children.append(xblock_entry)

return {level: xblock_children} if level else None


def sort_course_sections(course_key, data):
"""Retrieve and sort course sections based on the published course structure."""
course_blocks = modulestore().get_items(
course_key,
qualifiers={'category': 'course'},
revision=ModuleStoreEnum.RevisionOption.published_only
)

if not course_blocks or 'LinkCheckOutput' not in data or 'sections' not in data['LinkCheckOutput']:
return data # Return unchanged data if course_blocks or required keys are missing

sorted_section_ids = [section.location.block_id for section in course_blocks[0].get_children()]

sections_map = {section['id']: section for section in data['LinkCheckOutput']['sections']}
data['LinkCheckOutput']['sections'] = [
sections_map[section_id]
for section_id in sorted_section_ids
if section_id in sections_map
]

return data
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"""
Tests for course optimizer
"""
from unittest import mock
from unittest.mock import Mock

from cms.djangoapps.contentstore.tests.utils import CourseTestCase
from cms.djangoapps.contentstore.core.course_optimizer_provider import (
_update_node_tree_and_dictionary,
_create_dto_recursive
_create_dto_recursive,
sort_course_sections
)
from cms.djangoapps.contentstore.tasks import LinkState

Expand Down Expand Up @@ -222,3 +224,74 @@ def test_create_dto_recursive_returns_for_full_tree(self):
expected = _create_dto_recursive(mock_node_tree, mock_dictionary)

self.assertEqual(expected_result, expected)

@mock.patch('cms.djangoapps.contentstore.core.course_optimizer_provider.modulestore', autospec=True)
def test_returns_unchanged_data_if_no_course_blocks(self, mock_modulestore):
"""Test that the function returns unchanged data if no course blocks exist."""
mock_modulestore_instance = Mock()
mock_modulestore.return_value = mock_modulestore_instance
mock_modulestore_instance.get_items.return_value = []

data = {}
result = sort_course_sections("course-v1:Test+Course", data)
assert result == data # Should return the original data

@mock.patch('cms.djangoapps.contentstore.core.course_optimizer_provider.modulestore', autospec=True)
def test_returns_unchanged_data_if_linkcheckoutput_missing(self, mock_modulestore):
"""Test that the function returns unchanged data if 'LinkCheckOutput' is missing."""

mock_modulestore_instance = Mock()
mock_modulestore.return_value = mock_modulestore_instance

data = {'LinkCheckStatus': 'Uninitiated'} # No 'LinkCheckOutput'
mock_modulestore_instance.get_items.return_value = data

result = sort_course_sections("course-v1:Test+Course", data)
assert result == data

@mock.patch('cms.djangoapps.contentstore.core.course_optimizer_provider.modulestore', autospec=True)
def test_returns_unchanged_data_if_sections_missing(self, mock_modulestore):
"""Test that the function returns unchanged data if 'sections' is missing."""

mock_modulestore_instance = Mock()
mock_modulestore.return_value = mock_modulestore_instance

data = {'LinkCheckStatus': 'Success', 'LinkCheckOutput': {}} # No 'LinkCheckOutput'
mock_modulestore_instance.get_items.return_value = data

result = sort_course_sections("course-v1:Test+Course", data)
assert result == data

@mock.patch('cms.djangoapps.contentstore.core.course_optimizer_provider.modulestore', autospec=True)
def test_sorts_sections_correctly(self, mock_modulestore):
"""Test that the function correctly sorts sections based on published course structure."""

mock_course_block = Mock()
mock_course_block.get_children.return_value = [
Mock(location=Mock(block_id="section2")),
Mock(location=Mock(block_id="section3")),
Mock(location=Mock(block_id="section1")),
]

mock_modulestore_instance = Mock()
mock_modulestore.return_value = mock_modulestore_instance
mock_modulestore_instance.get_items.return_value = [mock_course_block]

data = {
"LinkCheckOutput": {
"sections": [
{"id": "section1", "name": "Intro"},
{"id": "section2", "name": "Advanced"},
{"id": "section3", "name": "Bonus"}, # Not in course structure
]
}
}

result = sort_course_sections("course-v1:Test+Course", data)
expected_sections = [
{"id": "section2", "name": "Advanced"},
{"id": "section3", "name": "Bonus"},
{"id": "section1", "name": "Intro"},
]

assert result["LinkCheckOutput"]["sections"] == expected_sections
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from rest_framework.response import Response
from user_tasks.models import UserTaskStatus

from cms.djangoapps.contentstore.core.course_optimizer_provider import get_link_check_data
from cms.djangoapps.contentstore.core.course_optimizer_provider import get_link_check_data, sort_course_sections
from cms.djangoapps.contentstore.rest_api.v0.serializers.course_optimizer import LinkCheckSerializer
from cms.djangoapps.contentstore.tasks import check_broken_links
from common.djangoapps.student.auth import has_course_author_access, has_studio_read_access
Expand Down Expand Up @@ -139,6 +139,7 @@ def get(self, request: Request, course_id: str):
self.permission_denied(request)

data = get_link_check_data(request, course_id)
serializer = LinkCheckSerializer(data)
data = sort_course_sections(course_key, data)

serializer = LinkCheckSerializer(data)
return Response(serializer.data)
Loading