feat: Add feature to display a global maintenance banner (#28673)

This is intended for use in the Studio OAuth transition (ARCHBOM-1887) but
may be useful in the future as well.
This commit is contained in:
Tim McCormack
2021-09-08 16:21:43 +00:00
committed by GitHub
parent 309cadc973
commit 035dfd8522
2 changed files with 103 additions and 0 deletions

View File

@@ -3,6 +3,8 @@ Unit tests for user messages.
"""
import warnings
import ddt
from django.contrib.messages.middleware import MessageMiddleware
from django.test import RequestFactory, TestCase
@@ -76,3 +78,59 @@ class UserMessagesTestCase(TestCase):
messages = list(PageLevelMessages.user_messages(self.request))
assert len(messages) == 1
assert messages[0].type == expected_message_type
def global_message_count(self):
"""
Count the number of times the global message appears in the user messages.
"""
expected_html = """<div class="message-content">I &lt;3 HTML-escaping</div>"""
messages = list(PageLevelMessages.user_messages(self.request))
return len(list(msg for msg in messages if expected_html in msg.message_html))
def test_global_message_off_by_default(self):
"""Verifies feature toggle."""
with self.settings(
GLOBAL_NOTICE_ENABLED=False,
GLOBAL_NOTICE_MESSAGE="I <3 HTML-escaping",
GLOBAL_NOTICE_TYPE='WARNING'
):
# Missing when feature disabled
assert self.global_message_count() == 0
def test_global_message_persistent(self):
"""Verifies global message is always included, when enabled."""
with self.settings(
GLOBAL_NOTICE_ENABLED=True,
GLOBAL_NOTICE_MESSAGE="I <3 HTML-escaping",
GLOBAL_NOTICE_TYPE='WARNING'
):
# Present with no other setup
assert self.global_message_count() == 1
# Present when other messages are present
PageLevelMessages.register_user_message(self.request, UserMessageType.INFO, "something else")
assert self.global_message_count() == 1
def test_global_message_error_isolation(self):
"""Verifies that any setting errors don't break the page, or other messages."""
with self.settings(
GLOBAL_NOTICE_ENABLED=True,
GLOBAL_NOTICE_MESSAGE=ThrowingMarkup(), # force an error
GLOBAL_NOTICE_TYPE='invalid'
):
PageLevelMessages.register_user_message(self.request, UserMessageType.WARNING, "something else")
# Doesn't throw, or even interfere with other messages,
# when given invalid settings
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
messages = list(PageLevelMessages.user_messages(self.request))
assert len(w) == 1
assert str(w[0].message) == "Could not register global notice: Exception('Some random error')"
assert len(messages) == 1
assert "something else" in messages[0].message_html
class ThrowingMarkup:
"""Class that raises an exception if markupsafe tries to get HTML from it."""
def __html__(self):
raise Exception("Some random error")

View File

@@ -15,11 +15,14 @@ There are two common use cases:
"""
import warnings
from abc import abstractmethod
from enum import Enum
from django.conf import settings
from django.contrib import messages
from django.utils.translation import ugettext as _
from edx_toggles.toggles import SettingToggle
from openedx.core.djangolib.markup import HTML, Text
@@ -181,12 +184,54 @@ class UserMessageCollection():
return (_create_user_message(message) for message in django_messages if self.get_namespace() in message.tags)
# .. toggle_name: GLOBAL_NOTICE_ENABLED
# .. toggle_implementation: SettingToggle
# .. toggle_default: False
# .. toggle_description: When enabled, show the contents of GLOBAL_NOTICE_MESSAGE
# as a message on every page. This is intended to be used as a way of
# communicating an upcoming or currently active maintenance window or to
# warn of known site issues. HTML is not supported for the message content,
# only plaintext. Message styling can be controlled with GLOBAL_NOTICE_TYPE,
# set to one of INFO, SUCCESS, WARNING, or ERROR (defaulting to INFO). Also
# see openedx.core.djangoapps.util.maintenance_banner.add_maintenance_banner
# for a variation that only shows a message on specific views.
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2021-09-08
GLOBAL_NOTICE_ENABLED = SettingToggle('GLOBAL_NOTICE_ENABLED', default=False)
class PageLevelMessages(UserMessageCollection):
"""
This set of messages appears as top page level messages.
"""
NAMESPACE = 'page_level_messages'
@classmethod
def user_messages(cls, request):
"""
Returns outstanding user messages, along with any persistent site-wide messages.
"""
msgs = list(super().user_messages(request))
# Add a global notice message to the returned list, if enabled.
try:
if GLOBAL_NOTICE_ENABLED.is_enabled():
if notice_message := getattr(settings, 'GLOBAL_NOTICE_MESSAGE', None):
notice_type_str = getattr(settings, 'GLOBAL_NOTICE_TYPE', None)
# If an invalid type is given, better to show a
# message with the default type than to fail to
# show it at all.
notice_type = getattr(UserMessageType, notice_type_str, UserMessageType.INFO)
msgs.append(UserMessage(
type=notice_type,
message_html=str(cls.get_message_html(Text(notice_message))),
))
except BaseException as e:
warnings.warn(f"Could not register global notice: {e!r}", UserWarning)
return msgs
@classmethod
def get_message_html(cls, body_html, title=None, dismissable=False, **kwargs):
"""