Skip to content
Draft
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
51 changes: 51 additions & 0 deletions common/djangoapps/util/tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
from django.test import override_settings
from openedx_filters import PipelineStep
from openedx_filters.learning.filters import InstructorDashboardTabsRequested

from common.djangoapps.util import course
from openedx.core.djangolib.testing.utils import skip_unless_lms
Expand Down Expand Up @@ -76,3 +77,53 @@ def test_course_about_page_url_requested_without_filter_configuration(self):
)

self.assertEqual(expected_course_about, course_about_url) # noqa: PT009


class TestInstructorDashCustomTab(PipelineStep):
"""
Utility class used when getting steps for pipeline.
"""

def run_filter(self, tabs, user, course_key): # pylint: disable=arguments-differ,unused-argument
"""Pipeline step that appends a custom instructor dashboard tab."""
result = {
"tabs": tabs + [{
"tab_id": "custom",
"title": "Custom Tab",
"url": f"/courses/{course_key}/instructor/custom",
"sort_order": 999,
}],
}
return result


class TestPreventTabsGenerationWithTabs(PipelineStep):
"""
Pipeline step that raises PreventTabsGeneration with a custom tabs list.
Used to test that the exception handler in get_tabs uses exc.tabs when present.
"""

def run_filter(self, tabs, user, course_key): # pylint: disable=arguments-differ,unused-argument
"""Pipeline step that raises PreventTabsGeneration with custom tabs."""
raise InstructorDashboardTabsRequested.PreventTabsGeneration(
"Preventing default tabs in favor of custom ones.",
tabs=[{
"tab_id": "plugin_tab",
"title": "Plugin Tab",
"url": f"/courses/{course_key}/instructor/plugin",
"sort_order": 5,
}],
)


class TestPreventTabsGenerationWithoutTabs(PipelineStep):
"""
Pipeline step that raises PreventTabsGeneration without a tabs list.
Used to test that the exception handler in get_tabs falls back to an empty list.
"""

def run_filter(self, tabs, user, course_key): # pylint: disable=arguments-differ,unused-argument
"""Pipeline step that raises PreventTabsGeneration without providing tabs."""
raise InstructorDashboardTabsRequested.PreventTabsGeneration(
"Preventing all tabs from being generated."
)
92 changes: 92 additions & 0 deletions lms/djangoapps/instructor/tests/views/test_api_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from unittest.mock import MagicMock, patch
from uuid import uuid4

from django.test import override_settings
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
Expand Down Expand Up @@ -692,3 +693,94 @@ def test_override_rejects_negative_score(self):
format='json',
)
assert response.status_code == status.HTTP_400_BAD_REQUEST


class CourseMetadataViewTestCase(ModuleStoreTestCase):
"""
Tests for GET /api/instructor/v2/courses/{course_id} with InstructorDashboardTabsRequested filter.
"""
def setUp(self):
super().setUp()
self.client = APIClient()
self.course = CourseFactory.create()
self.instructor = InstructorFactory.create(course_key=self.course.id)
self.client.force_authenticate(user=self.instructor)

@override_settings(
OPEN_EDX_FILTERS_CONFIG={
"org.openedx.learning.instructor.dashboard.tabs.requested.v1": {
"pipeline": [
"common.djangoapps.util.tests.test_filters.TestInstructorDashCustomTab",
],
"fail_silently": False,
},
},
)
def test_tabs_filter_adds_custom_tab(self):
"""Test that override settings drive a custom instructor dashboard tab."""
url = reverse("instructor_api_v2:course_metadata", kwargs={"course_id": str(self.course.id)})
response = self.client.get(url)

assert response.status_code == status.HTTP_200_OK
data = response.json()
tabs_by_id = {tab["tab_id"]: tab for tab in data["tabs"]}

assert "course_info" in tabs_by_id
assert "custom" in tabs_by_id
assert tabs_by_id["custom"] == {
"tab_id": "custom",
"title": "Custom Tab",
"url": f"/courses/{self.course.id}/instructor/custom",
"sort_order": 999,
}

@override_settings(
OPEN_EDX_FILTERS_CONFIG={
"org.openedx.learning.instructor.dashboard.tabs.requested.v1": {
"pipeline": [
"common.djangoapps.util.tests.test_filters.TestPreventTabsGenerationWithTabs",
],
"fail_silently": False,
},
},
)
def test_tabs_filter_prevent_tabs_generation_with_custom_tabs(self):
"""
Test that when PreventTabsGeneration is raised with a tabs attribute,
the serializer uses those custom tabs instead of the default ones.
"""
url = reverse("instructor_api_v2:course_metadata", kwargs={"course_id": str(self.course.id)})
with self.assertLogs('openedx_filters.tooling', level='ERROR'):
response = self.client.get(url)

assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["tabs"] == [{
"tab_id": "plugin_tab",
"title": "Plugin Tab",
"url": f"/courses/{self.course.id}/instructor/plugin",
"sort_order": 5,
}]

@override_settings(
OPEN_EDX_FILTERS_CONFIG={
"org.openedx.learning.instructor.dashboard.tabs.requested.v1": {
"pipeline": [
"common.djangoapps.util.tests.test_filters.TestPreventTabsGenerationWithoutTabs",
],
"fail_silently": False,
},
},
)
def test_tabs_filter_prevent_tabs_generation_without_tabs_attr(self):
"""
Test that when PreventTabsGeneration is raised without a tabs attribute,
the serializer falls back to an empty list.
"""
url = reverse("instructor_api_v2:course_metadata", kwargs={"course_id": str(self.course.id)})
with self.assertLogs('openedx_filters.tooling', level='ERROR'):
response = self.client.get(url)

assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["tabs"] == []
17 changes: 15 additions & 2 deletions lms/djangoapps/instructor/views/serializers_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from django.utils.html import escape
from django.utils.translation import gettext as _
from edx_when.api import is_enabled_for_course
from openedx_filters.learning.filters import InstructorDashboardTabsRequested
from rest_framework import serializers

from common.djangoapps.course_modes.models import CourseMode
Expand Down Expand Up @@ -305,6 +306,19 @@ def get_tabs(self, data):
'sort_order': 110,
})

try:
# .. filter_implemented_name: InstructorDashboardTabsRequested
# .. filter_type: org.openedx.learning.instructor.dashboard.tabs.requested.v1
filtered_tabs = InstructorDashboardTabsRequested.run_filter(
tabs=tabs,
user=request.user,
course_key=course_key
)
custom_tabs = filtered_tabs if filtered_tabs is not None else tabs
except InstructorDashboardTabsRequested.PreventTabsGeneration as exc:
# Plugin provided custom tabs or prevented tab generation
custom_tabs = getattr(exc, 'tabs', None) or []

# We provide the tabs in a specific order based on how it was
# historically presented in the frontend. The frontend can use
# this info or choose to ignore the ordering.
Expand All @@ -322,8 +336,7 @@ def get_tabs(self, data):
'special_exams',
]
order_index = {tab: i for i, tab in enumerate(tabs_order)}
tabs = sorted(tabs, key=lambda x: order_index.get(x['tab_id'], float("inf")))
return tabs
return sorted(custom_tabs, key=lambda x: order_index.get(x['tab_id'], float("inf")))

def get_course_id(self, data):
"""Get course ID as string."""
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ openedx-events==11.2.0
# openedx-authz
# openedx-core
# ora2
openedx-filters==3.1.0
openedx-filters==3.4.0
# via
# -r requirements/edx/kernel.in
# edx-enterprise
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/development.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1413,7 +1413,7 @@ openedx-events==11.2.0
# openedx-authz
# openedx-core
# ora2
openedx-filters==3.1.0
openedx-filters==3.4.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/doc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,7 @@ openedx-events==11.2.0
# openedx-authz
# openedx-core
# ora2
openedx-filters==3.1.0
openedx-filters==3.4.0
# via
# -r requirements/edx/base.txt
# edx-enterprise
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/testing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1081,7 +1081,7 @@ openedx-events==11.2.0
# openedx-authz
# openedx-core
# ora2
openedx-filters==3.1.0
openedx-filters==3.4.0
# via
# -r requirements/edx/base.txt
# edx-enterprise
Expand Down
Loading