Skip to content

Commit

Permalink
Rename and move class
Browse files Browse the repository at this point in the history
  • Loading branch information
jedcunningham committed Sep 20, 2021
1 parent a371bbd commit b8f0790
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 135 deletions.
90 changes: 0 additions & 90 deletions airflow/models/flash_message.py

This file was deleted.

18 changes: 9 additions & 9 deletions airflow/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,18 +549,18 @@ def initialize():
# instances
MASK_SECRETS_IN_LOGS = False

# Display flash messages on the dashboard
# Display alerts on the dashboard
# Useful for warning about setup issues or announcing changes to end users
# List of FlashMessage's, which allows for specifiying the message, category, and roles the
# List of UIAlerts, which allows for specifiying the message, category, and roles the
# message should be shown to. For example:
# from airflow.models.flash_message = FlashMessage
# from airflow.www.utils import UIAlert
#
# DASHBOARD_FLASH_MESSAGES = [
# FlashMessage("Welcome to Airflow"), # All users
# FlashMessage("Airflow update happening next week", roles=["User"]), # Only users with the User role
# DASHBOARD_UIALERTS = [
# UIAlert("Welcome to Airflow"), # All users
# UIAlert("Airflow update happening next week", roles=["User"]), # Only users with the User role
# # A flash message with html:
# FlashMessage('Visit <a href="http://airflow.apache.org">airflow.apache.org</a>', html=True),
# UIAlert('Visit <a href="http://airflow.apache.org">airflow.apache.org</a>', html=True),
# ]
#
# DASHBOARD_FLASH_MESSAGES: List["FlashMessage"]
DASHBOARD_FLASH_MESSAGES = []
# DASHBOARD_UIALERTS: List["UIAlert"]
DASHBOARD_UIALERTS = []
61 changes: 60 additions & 1 deletion airflow/www/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import json
import textwrap
import time
from typing import Any
from typing import Any, List, Optional, Union
from urllib.parse import urlencode

import markdown
Expand Down Expand Up @@ -507,3 +507,62 @@ def get_col_default(self, col_name: str) -> Any:
FieldConverter.conversion_table = (
('is_utcdatetime', DateTimeWithTimezoneField, AirflowDateTimePickerWidget),
) + FieldConverter.conversion_table


class UIAlert:
"""
Helper for alerts messages shown on the UI
:param message: The message to display, either a string or Markup
:type message: Union[str,Markup]
:param category: The category of the message, one of "info", "warning", "error", or any custom category.
Defaults to "info".
:type category: str
:param roles: List of roles that should be shown the message. If ``None``, show to all users.
:type roles: Optional[List[str]]
:param html: Whether the message has safe html markup in it. Defaults to False.
:type html: bool
For example, show a message to all users:
.. code-block:: python
UIAlert("Welcome to Airflow")
Or only for users with the User role:
.. code-block:: python
UIAlert("Airflow update happening next week", roles=["User"])
You can also pass html in the message:
.. code-block:: python
UIAlert('Visit <a href="https://airflow.apache.org">airflow.apache.org</a>', html=True)
# or safely escape part of the message
# (more details: https://markupsafe.palletsprojects.com/en/2.0.x/formatting/)
UIAlert(Markup("Welcome <em>%s</em>") % ("John & Jane Doe",))
"""

def __init__(
self,
message: Union[str, Markup],
category: str = "info",
roles: Optional[List[str]] = None,
html: bool = False,
):
self.category = category
self.roles = roles
self.html = html
self.message = Markup(message) if html else message

def should_show(self, securitymanager) -> bool:
"""Determine if the user should see the message based on their role membership"""
if self.roles:
user_roles = {r.name for r in securitymanager.get_user_roles()}
if not user_roles.intersection(set(self.roles)):
return False
return True
4 changes: 2 additions & 2 deletions airflow/www/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -565,9 +565,9 @@ def index(self):
category="warning",
)

for fm in settings.DASHBOARD_FLASH_MESSAGES:
for fm in settings.DASHBOARD_UIALERTS:
if fm.should_show(current_app.appbuilder.sm):
fm.flash()
flash(fm.message, fm.category)

hide_paused_dags_by_default = conf.getboolean('webserver', 'hide_paused_dags_by_default')
default_dag_run = conf.getint('webserver', 'default_dag_run_display_number')
Expand Down
28 changes: 13 additions & 15 deletions docs/apache-airflow/howto/customize-ui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,13 @@ After
.. image:: ../img/change-site-title/example_instance_name_configuration.png


Add custom flash messages on the dashboard
Add custom alert messages on the dashboard
------------------------------------------

.. versionadded:: 2.2.0

Extra flash messages can be shown on the UI dashboard. This can be useful for warning about setup issues
or announcing changes to end users. The following example shows how to add a simple flash message:
Extra alert messages can be shown on the UI dashboard. This can be useful for warning about setup issues
or announcing changes to end users. The following example shows how to add a simple alert message:

1. Create ``airflow_local_settings.py`` file and put in on ``$PYTHONPATH`` or
to ``$AIRFLOW_HOME/config`` folder. (Airflow adds ``$AIRFLOW_HOME/config`` on ``PYTHONPATH`` when
Expand All @@ -134,25 +134,23 @@ or announcing changes to end users. The following example shows how to add a sim

.. code-block:: python
from airflow.models.flash_message import FlashMessage
from airflow.www.utils import UIAlert
DASHBOARD_FLASH_MESSAGES = [
FlashMessage("Welcome to Airflow"),
DASHBOARD_UIALERTS = [
UIAlert("Welcome to Airflow"),
]
3. Restart Airflow Webserver, and you should now see:

.. image:: ../img/ui-flash-message.png
.. image:: ../img/ui-alert-message.png

You can also control the category of the flash message as well the roles it should be shown to.
You can also control the category of the alert message as well the roles it should be shown to.
For example, to show a warning message to users in the ``User`` role:

.. code-block:: python
DASHBOARD_FLASH_MESSAGES = [
FlashMessage(
"Airflow update happening next week", category="warning", roles=["User"]
),
DASHBOARD_UIALERTS = [
UIAlert("Airflow update happening next week", category="warning", roles=["User"]),
]
HTML can also be included in the messages, though care must be taken to ensure it is done safely.
Expand All @@ -161,9 +159,9 @@ information, see `String Formatting in the MarkupSafe docs <https://markupsafe.p

.. code-block:: python
DASHBOARD_FLASH_MESSAGES = [
FlashMessage(
DASHBOARD_UIALERTS = [
UIAlert(
'Visit <a href="https://airflow.apache.org">airflow.apache.org</a>', html=True
),
FlashMessage(Markup("Welcome <em>%s</em>") % ("John & Jane Doe",)),
UIAlert(Markup("Welcome <em>%s</em>") % ("John & Jane Doe",)),
]
36 changes: 18 additions & 18 deletions tests/www/views/test_views_home.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@
import pytest

from airflow.dag_processing.processor import DagFileProcessor
from airflow.models.flash_message import FlashMessage
from airflow.security import permissions
from airflow.utils.session import create_session
from airflow.utils.state import State
from airflow.www.utils import UIAlert
from airflow.www.views import FILTER_STATUS_COOKIE, FILTER_TAGS_COOKIE
from tests.test_utils.api_connexion_utils import create_user
from tests.test_utils.db import clear_db_dags, clear_db_import_errors, clear_db_serialized_dags
Expand Down Expand Up @@ -192,17 +192,17 @@ def test_home_robots_header_in_response(user_client):
@pytest.mark.parametrize(
"client, flash_message, expected",
[
("user_client", FlashMessage("hello world"), True),
("user_client", FlashMessage("hello world", roles=["User"]), True),
("user_client", FlashMessage("hello world", roles=["User", "Admin"]), True),
("user_client", FlashMessage("hello world", roles=["Admin"]), False),
("admin_client", FlashMessage("hello world"), True),
("admin_client", FlashMessage("hello world", roles=["Admin"]), True),
("admin_client", FlashMessage("hello world", roles=["User", "Admin"]), True),
("user_client", UIAlert("hello world"), True),
("user_client", UIAlert("hello world", roles=["User"]), True),
("user_client", UIAlert("hello world", roles=["User", "Admin"]), True),
("user_client", UIAlert("hello world", roles=["Admin"]), False),
("admin_client", UIAlert("hello world"), True),
("admin_client", UIAlert("hello world", roles=["Admin"]), True),
("admin_client", UIAlert("hello world", roles=["User", "Admin"]), True),
],
)
def test_dashboard_flash_messages_role_filtering(request, client, flash_message, expected):
with mock.patch("airflow.settings.DASHBOARD_FLASH_MESSAGES", [flash_message]):
with mock.patch("airflow.settings.DASHBOARD_UIALERTS", [flash_message]):
resp = request.getfixturevalue(client).get("home", follow_redirects=True)
if expected:
check_content_in_response(flash_message.message, resp)
Expand All @@ -212,11 +212,11 @@ def test_dashboard_flash_messages_role_filtering(request, client, flash_message,

def test_dashboard_flash_messages_many(user_client):
messages = [
FlashMessage("hello world"),
FlashMessage("im_not_here", roles=["Admin"]),
FlashMessage("_hello_world_"),
UIAlert("hello world"),
UIAlert("im_not_here", roles=["Admin"]),
UIAlert("_hello_world_"),
]
with mock.patch("airflow.settings.DASHBOARD_FLASH_MESSAGES", messages):
with mock.patch("airflow.settings.DASHBOARD_UIALERTS", messages):
resp = user_client.get("home", follow_redirects=True)
check_content_in_response("hello world", resp)
check_content_not_in_response("im_not_here", resp)
Expand All @@ -227,20 +227,20 @@ def test_dashboard_flash_messages_markup(user_client):
link = '<a href="http://example.com">hello world</a>'
user_input = flask.Markup("Hello <em>%s</em>") % ("foo&bar",)
messages = [
FlashMessage(link, html=True),
FlashMessage(user_input),
UIAlert(link, html=True),
UIAlert(user_input),
]
with mock.patch("airflow.settings.DASHBOARD_FLASH_MESSAGES", messages):
with mock.patch("airflow.settings.DASHBOARD_UIALERTS", messages):
resp = user_client.get("home", follow_redirects=True)
check_content_in_response(link, resp)
check_content_in_response(user_input, resp)


def test_dashboard_flash_messages_type(user_client):
messages = [
FlashMessage("hello world", category="foo"),
UIAlert("hello world", category="foo"),
]
with mock.patch("airflow.settings.DASHBOARD_FLASH_MESSAGES", messages):
with mock.patch("airflow.settings.DASHBOARD_UIALERTS", messages):
resp = user_client.get("home", follow_redirects=True)
check_content_in_response("hello world", resp)
check_content_in_response("alert-foo", resp)

0 comments on commit b8f0790

Please sign in to comment.