You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by ka...@apache.org on 2021/02/17 11:49:25 UTC

[airflow] branch master updated: Add more flexibility with FAB menu links (#13903)

This is an automated email from the ASF dual-hosted git repository.

kaxilnaik pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/master by this push:
     new ef0c17b  Add more flexibility with FAB menu links (#13903)
ef0c17b is described below

commit ef0c17baa7ce04f7d8adfca5911f1b85b1a9857a
Author: Jed Cunningham <66...@users.noreply.github.com>
AuthorDate: Wed Feb 17 04:49:04 2021 -0700

    Add more flexibility with FAB menu links (#13903)
    
    Airflow can be more flexible with the links plugins are allowed to add. Currently, you cannot add a top level link, a link with a label, or even without providing a category_icon (which isn't used anyways).
    
    This PR gives plugin authors the flexibility to add any link FAB supports.
---
 airflow/www/extensions/init_views.py  |  9 ++-------
 docs/apache-airflow/plugins.rst       | 19 ++++++++++++-------
 tests/plugins/test_plugin.py          | 12 ++++++++----
 tests/plugins/test_plugins_manager.py | 27 +++++++++++++++++++--------
 tests/www/test_views.py               |  2 +-
 5 files changed, 42 insertions(+), 27 deletions(-)

diff --git a/airflow/www/extensions/init_views.py b/airflow/www/extensions/init_views.py
index a364773..4e58aab 100644
--- a/airflow/www/extensions/init_views.py
+++ b/airflow/www/extensions/init_views.py
@@ -121,13 +121,8 @@ def init_plugins(app):
             appbuilder.add_view_no_menu(view["view"])
 
     for menu_link in sorted(plugins_manager.flask_appbuilder_menu_links, key=lambda x: x["name"]):
-        log.debug("Adding menu link %s", menu_link["name"])
-        appbuilder.add_link(
-            menu_link["name"],
-            href=menu_link["href"],
-            category=menu_link["category"],
-            category_icon=menu_link["category_icon"],
-        )
+        log.debug("Adding menu link %s to %s", menu_link["name"], menu_link["href"])
+        appbuilder.add_link(**menu_link)
 
     for blue_print in plugins_manager.flask_blueprints:
         log.debug("Adding blueprint %s:%s", blue_print["name"], blue_print["blueprint"].import_name)
diff --git a/docs/apache-airflow/plugins.rst b/docs/apache-airflow/plugins.rst
index 80708b9..687270a 100644
--- a/docs/apache-airflow/plugins.rst
+++ b/docs/apache-airflow/plugins.rst
@@ -115,7 +115,7 @@ looks like:
         flask_blueprints = []
         # A list of dictionaries containing FlaskAppBuilder BaseView object and some metadata. See example below
         appbuilder_views = []
-        # A list of dictionaries containing FlaskAppBuilder BaseView object and some metadata. See example below
+        # A list of dictionaries containing kwargs for FlaskAppBuilder add_link. See example below
         appbuilder_menu_items = []
         # A callback to perform actions when airflow starts and the plugin is loaded.
         # NOTE: Ensure your plugin has *args, and **kwargs in the method definition
@@ -210,11 +210,16 @@ definitions in Airflow.
         "view": v_appbuilder_nomenu_view
     }
 
-    # Creating a flask appbuilder Menu Item
-    appbuilder_mitem = {"name": "Google",
-                        "category": "Search",
-                        "category_icon": "fa-th",
-                        "href": "https://www.google.com"}
+    # Creating flask appbuilder Menu Items
+    appbuilder_mitem = {
+        "name": "Google",
+        "href": "https://www.google.com",
+        "category": "Search",
+    }
+    appbuilder_mitem_toplevel = {
+        "name": "Apache",
+        "href": "https://www.apache.org/",
+    }
 
     # A global operator extra link that redirect you to
     # task logs stored in S3
@@ -247,7 +252,7 @@ definitions in Airflow.
         macros = [plugin_macro]
         flask_blueprints = [bp]
         appbuilder_views = [v_appbuilder_package, v_appbuilder_nomenu_package]
-        appbuilder_menu_items = [appbuilder_mitem]
+        appbuilder_menu_items = [appbuilder_mitem, appbuilder_mitem_toplevel]
         global_operator_extra_links = [GoogleLink(),]
         operator_extra_links = [S3LogLink(), ]
 
diff --git a/tests/plugins/test_plugin.py b/tests/plugins/test_plugin.py
index ae725f3..d52d8e5 100644
--- a/tests/plugins/test_plugin.py
+++ b/tests/plugins/test_plugin.py
@@ -77,12 +77,16 @@ v_appbuilder_package = {"name": "Test View", "category": "Test Plugin", "view":
 
 v_nomenu_appbuilder_package = {"view": v_appbuilder_view}
 
-# Creating a flask appbuilder Menu Item
+# Creating flask appbuilder Menu Items
 appbuilder_mitem = {
     "name": "Google",
-    "category": "Search",
-    "category_icon": "fa-th",
     "href": "https://www.google.com",
+    "category": "Search",
+}
+appbuilder_mitem_toplevel = {
+    "name": "apache",
+    "href": "https://www.apache.org/",
+    "label": "The Apache Software Foundation",
 }
 
 # Creating a flask blueprint to intergrate the templates and static folder
@@ -105,7 +109,7 @@ class AirflowTestPlugin(AirflowPlugin):
     macros = [plugin_macro]
     flask_blueprints = [bp]
     appbuilder_views = [v_appbuilder_package]
-    appbuilder_menu_items = [appbuilder_mitem]
+    appbuilder_menu_items = [appbuilder_mitem, appbuilder_mitem_toplevel]
     global_operator_extra_links = [
         AirflowLink(),
         GithubLink(),
diff --git a/tests/plugins/test_plugins_manager.py b/tests/plugins/test_plugins_manager.py
index d454754..f730f17 100644
--- a/tests/plugins/test_plugins_manager.py
+++ b/tests/plugins/test_plugins_manager.py
@@ -77,21 +77,32 @@ class TestPluginsRBAC(unittest.TestCase):
             assert len(plugin_views) == 1
 
     def test_flaskappbuilder_menu_links(self):
-        from tests.plugins.test_plugin import appbuilder_mitem
+        from tests.plugins.test_plugin import appbuilder_mitem, appbuilder_mitem_toplevel
 
-        # menu item should exist matching appbuilder_mitem
-        links = [
+        # menu item (category) should exist matching appbuilder_mitem.category
+        categories = [
             menu_item
             for menu_item in self.appbuilder.menu.menu
             if menu_item.name == appbuilder_mitem['category']
         ]
+        assert len(categories) == 1
 
-        assert len(links) == 1
+        # menu link should be a child in the category
+        category = categories[0]
+        assert category.name == appbuilder_mitem['category']
+        assert category.childs[0].name == appbuilder_mitem['name']
+        assert category.childs[0].href == appbuilder_mitem['href']
 
-        # menu link should also have a link matching the name of the package.
-        link = links[0]
-        assert link.name == appbuilder_mitem['category']
-        assert link.childs[0].name == appbuilder_mitem['name']
+        # a top level link isn't nested in a category
+        top_levels = [
+            menu_item
+            for menu_item in self.appbuilder.menu.menu
+            if menu_item.name == appbuilder_mitem_toplevel['name']
+        ]
+        assert len(top_levels) == 1
+        link = top_levels[0]
+        assert link.href == appbuilder_mitem_toplevel['href']
+        assert link.label == appbuilder_mitem_toplevel['label']
 
     def test_app_blueprints(self):
         from tests.plugins.test_plugin import bp
diff --git a/tests/www/test_views.py b/tests/www/test_views.py
index 98aef0c..37a97af 100644
--- a/tests/www/test_views.py
+++ b/tests/www/test_views.py
@@ -488,7 +488,7 @@ class TestAirflowBaseViews(TestBase):
         )
 
     def test_index(self):
-        with assert_queries_count(42):
+        with assert_queries_count(43):
             resp = self.client.get('/', follow_redirects=True)
         self.check_content_in_response('DAGs', resp)