feat: Add support for course dashboard redirect for notices app

[MICROBA-1520]
- Update course dashboard to check its context for unack'd notices from the notices app. If so, redirect the learner to the first unack'd notice.

(reintroduces earlier changes that were reverted)
This commit is contained in:
Justin Hynes
2021-10-20 12:46:35 -04:00
parent 4854af6336
commit ae18a96090
2 changed files with 133 additions and 0 deletions

View File

@@ -13,6 +13,7 @@ from unittest.mock import patch
import ddt
from completion.test_utils import CompletionWaffleTestMixin, submit_completions_for_testing
from django.conf import settings
from django.test.utils import override_settings
from django.urls import reverse
from django.utils.timezone import now
from milestones.tests.utils import MilestonesTestCaseMixin
@@ -26,6 +27,7 @@ from common.djangoapps.student.helpers import DISABLE_UNENROLL_CERT_STATES
from common.djangoapps.student.models import CourseEnrollment, UserProfile
from common.djangoapps.student.signals import REFUND_ORDER
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from common.djangoapps.student.views.dashboard import check_for_unacknowledged_notices
from common.djangoapps.util.milestones_helpers import (
get_course_milestones,
remove_prerequisite_course,
@@ -905,3 +907,114 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin,
assert expected_button in dashboard_html
assert unexpected_button not in dashboard_html
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid for the LMS')
@unittest.skipUnless(settings.FEATURES.get("ENABLE_NOTICES"), 'Notices plugin is not enabled')
class TestCourseDashboardNoticesRedirects(SharedModuleStoreTestCase):
"""
Tests for the Dashboard redirect functionality introduced via the Notices plugin.
"""
def setUp(self):
super().setUp()
self.user = UserFactory()
self.client.login(username=self.user.username, password=PASSWORD)
self.path = reverse('dashboard')
def test_check_for_unacknowledged_notices(self):
"""
Happy path. Verifies that we return a URL in the proper form for a user that has an unack'd Notice.
"""
context = {
"plugins": {
"notices": {
"unacknowledged_notices": [
'/notices/render/1/',
'/notices/render/2/',
],
}
}
}
path = reverse("notices:notice-detail", kwargs={"pk": 1})
expected_results = f"{settings.LMS_ROOT_URL}{path}?next={settings.LMS_ROOT_URL}/dashboard/"
results = check_for_unacknowledged_notices(context)
assert results == expected_results
def test_check_for_unacknowledged_notices_no_unacknowledged_notices(self):
"""
Verifies that we will return None if the user has no unack'd Notices in the plugin context data.
"""
context = {
"plugins": {
"notices": {
"unacknowledged_notices": [],
}
}
}
results = check_for_unacknowledged_notices(context)
assert results is None
def test_check_for_unacknowledged_notices_incorrect_data(self):
"""
Verifies that we will return None (and no Exceptions are thrown) if the plugin context data doesn't match the
expected form.
"""
context = {
"plugins": {
"notices": {
"incorrect_key": [
'/notices/render/1/',
'/notices/render/2/',
],
}
}
}
results = check_for_unacknowledged_notices(context)
assert results is None
@patch('common.djangoapps.student.views.dashboard.check_for_unacknowledged_notices')
def test_user_with_unacknowledged_notice(self, mock_notices):
"""
Verifies that we will redirect the learner to the URL returned from the `check_for_unacknowledged_notices`
function.
"""
mock_notices.return_value = reverse("about")
with override_settings(FEATURES={**settings.FEATURES, 'ENABLE_NOTICES': True}):
response = self.client.get(self.path)
assert response.status_code == 302
assert response.url == "/about"
mock_notices.assert_called_once()
@patch('common.djangoapps.student.views.dashboard.check_for_unacknowledged_notices')
def test_user_with_unacknowledged_notice_no_notices(self, mock_notices):
"""
Verifies that we will NOT redirect the user if the result of calling the `check_for_unacknowledged_notices`
function is None.
"""
mock_notices.return_value = None
with override_settings(FEATURES={**settings.FEATURES, 'ENABLE_NOTICES': True}):
response = self.client.get(self.path)
assert response.status_code == 200
mock_notices.assert_called_once()
@patch('common.djangoapps.student.views.dashboard.check_for_unacknowledged_notices')
def test_user_with_unacknowledged_notice_plugin_disabled(self, mock_notices):
"""
Verifies that the `check_for_unacknowledged_notices` function is NOT called if the feature is disabled.
"""
mock_notices.return_value = None
with override_settings(FEATURES={**settings.FEATURES, 'ENABLE_NOTICES': False}):
response = self.client.get(self.path)
assert response.status_code == 200
mock_notices.assert_not_called()

View File

@@ -472,6 +472,22 @@ def get_dashboard_course_limit():
return course_limit
def check_for_unacknowledged_notices(context):
"""
Checks the notices apps plugin context to see if there are any unacknowledged notices the user needs to take action
on. If so, build a redirect url to the first unack'd notice.
"""
notice_url = None
notices = context.get("plugins", {}).get("notices", {}).get("unacknowledged_notices")
if notices:
# We will only show one notice to the user one at a time. Build a redirect URL to the first notice in the
# list of unacknowledged notices.
notice_url = f"{settings.LMS_ROOT_URL}{notices[0]}?next={settings.LMS_ROOT_URL}/dashboard/"
return notice_url
@login_required
@ensure_csrf_cookie
@add_maintenance_banner
@@ -810,6 +826,10 @@ def student_dashboard(request): # lint-amnesty, pylint: disable=too-many-statem
)
context.update(context_from_plugins)
notice_url = check_for_unacknowledged_notices(context)
if notice_url:
return redirect(notice_url)
course = None
context.update(
get_experiment_user_metadata_context(