This is an automated email from the ASF dual-hosted git repository. ash pushed a commit to branch v2-0-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 04ae0f6c217b5adef083c17217d2d6b21ae3bb75 Author: Jed Cunningham <[email protected]> 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. (cherry picked from commit ef0c17baa7ce04f7d8adfca5911f1b85b1a9857a) --- 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 f9736e6..0dcf8c7 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 5011547..efcb46e 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)
