[ 
https://issues.apache.org/jira/browse/AIRFLOW-2744?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16660917#comment-16660917
 ] 

ASF GitHub Bot commented on AIRFLOW-2744:
-----------------------------------------

ashb closed pull request #4036: [AIRFLOW-2744] Allow RBAC to accept plugins for 
views and links.
URL: https://github.com/apache/incubator-airflow/pull/4036
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/airflow/plugins_manager.py b/airflow/plugins_manager.py
index ad630701de..c589e9b5a9 100644
--- a/airflow/plugins_manager.py
+++ b/airflow/plugins_manager.py
@@ -49,6 +49,8 @@ class AirflowPlugin(object):
     admin_views = []
     flask_blueprints = []
     menu_links = []
+    appbuilder_views = []
+    appbuilder_menu_items = []
 
     @classmethod
     def validate(cls):
@@ -120,6 +122,8 @@ def make_module(name, objects):
 admin_views = []
 flask_blueprints = []
 menu_links = []
+flask_appbuilder_views = []
+flask_appbuilder_menu_links = []
 
 for p in plugins:
     operators_modules.append(
@@ -135,3 +139,5 @@ def make_module(name, objects):
     admin_views.extend(p.admin_views)
     flask_blueprints.extend(p.flask_blueprints)
     menu_links.extend(p.menu_links)
+    flask_appbuilder_views.extend(p.appbuilder_views)
+    flask_appbuilder_menu_links.extend(p.appbuilder_menu_items)
diff --git a/airflow/www_rbac/app.py b/airflow/www_rbac/app.py
index 392dce1b31..e7f5a1e976 100644
--- a/airflow/www_rbac/app.py
+++ b/airflow/www_rbac/app.py
@@ -17,6 +17,7 @@
 # specific language governing permissions and limitations
 # under the License.
 #
+import logging
 import socket
 import six
 
@@ -37,6 +38,7 @@
 appbuilder = None
 csrf = CSRFProtect()
 
+log = logging.getLogger(__name__)
 
 def create_app(config=None, session=None, testing=False, app_name="Airflow"):
     global app, appbuilder
@@ -135,6 +137,24 @@ def init_views(appbuilder):
                                 category='About',
                                 category_icon='fa-th')
 
+            def integrate_plugins():
+                """Integrate plugins to the context"""
+                from airflow.plugins_manager import (
+                    flask_appbuilder_views, flask_appbuilder_menu_links)
+
+                for v in flask_appbuilder_views:
+                    log.debug("Adding view %s", v["name"])
+                    appbuilder.add_view(v["view"],
+                                        v["name"],
+                                        category=v["category"])
+                for ml in sorted(flask_appbuilder_menu_links, key=lambda x: 
x["name"]):
+                    log.debug("Adding menu link %s", ml["name"])
+                    appbuilder.add_link(ml["name"],
+                                        href=ml["href"],
+                                        category=ml["category"],
+                                        category_icon=ml["category_icon"])
+
+            integrate_plugins()
             # Garbage collect old permissions/views after they have been 
modified.
             # Otherwise, when the name of a view or menu is changed, the 
framework
             # will add the new Views and Menus names to the backend, but will 
not
diff --git a/docs/plugins.rst b/docs/plugins.rst
index 3f1f7ee804..eb44247950 100644
--- a/docs/plugins.rst
+++ b/docs/plugins.rst
@@ -72,10 +72,15 @@ looks like:
         # A list of objects created from a class derived
         # from flask_admin.BaseView
         admin_views = []
-        # A list of Blueprint object created from flask.Blueprint
+        # A list of Blueprint object created from flask.Blueprint. For use 
with the flask_admin based GUI
         flask_blueprints = []
-        # A list of menu links (flask_admin.base.MenuLink)
+        # A list of menu links (flask_admin.base.MenuLink). For use with the 
flask_admin based GUI
         menu_links = []
+        # 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
+        appbuilder_menu_items = []
+
 
 
 You can derive it by inheritance (please refer to the example below).
@@ -159,6 +164,22 @@ definitions in Airflow.
         name='Test Menu Link',
         url='https://airflow.incubator.apache.org/')
 
+    # Creating a flask appbuilder BaseView
+    class TestAppBuilderBaseView(AppBuilderBaseView):
+        @expose("/")
+        def test(self):
+            return self.render("test_plugin/test.html", content="Hello 
galaxy!")
+    v_appbuilder_view = TestAppBuilderBaseView()
+    v_appbuilder_package = {"name": "Test View",
+                            "category": "Test Plugin",
+                            "view": v_appbuilder_view}
+
+    # Creating a flask appbuilder Menu Item
+    appbuilder_mitem = {"name": "Google",
+                        "category": "Search",
+                        "category_icon": "fa-th",
+                        "href": "https://www.google.com"}
+
     # Defining the plugin class
     class AirflowTestPlugin(AirflowPlugin):
         name = "test_plugin"
@@ -170,3 +191,13 @@ definitions in Airflow.
         admin_views = [v]
         flask_blueprints = [bp]
         menu_links = [ml]
+        appbuilder_views = [v_appbuilder_package]
+        appbuilder_menu_items = [appbuilder_mitem]
+
+
+Note on role based views
+------------------------
+
+Airflow 1.10 introduced role based views using FlaskAppBuilder. You can 
configure which UI is used by setting
+rbac = True. To support plugin views and links for both versions of the UI and 
maintain backwards compatibility,
+the fields appbuilder_views and appbuilder_menu_items were added to the 
AirflowTestPlugin class.
diff --git a/tests/plugins/test_plugin.py b/tests/plugins/test_plugin.py
index 7b8bc4f61c..aaf1796e68 100644
--- a/tests/plugins/test_plugin.py
+++ b/tests/plugins/test_plugin.py
@@ -23,6 +23,7 @@
 from flask import Blueprint
 from flask_admin import BaseView, expose
 from flask_admin.base import MenuLink
+from flask_appbuilder import BaseView as AppBuilderBaseView
 
 # Importing base classes that we need to derive
 from airflow.hooks.base_hook import BaseHook
@@ -67,6 +68,28 @@ def test(self):
 
 v = TestView(category="Test Plugin", name="Test View")
 
+
+# Creating a flask appbuilder BaseView
+class TestAppBuilderBaseView(AppBuilderBaseView):
+    default_view = "test"
+
+    @expose("/")
+    def test(self):
+        return self.render("test_plugin/test.html", content="Hello galaxy!")
+
+
+v_appbuilder_view = TestAppBuilderBaseView()
+v_appbuilder_package = {"name": "Test View",
+                        "category": "Test Plugin",
+                        "view": v_appbuilder_view}
+
+# Creating a flask appbuilder Menu Item
+appbuilder_mitem = {"name": "Google",
+                    "category": "Search",
+                    "category_icon": "fa-th",
+                    "href": "https://www.google.com"}
+
+
 # Creating a flask blueprint to intergrate the templates and static folder
 bp = Blueprint(
     "test_plugin", __name__,
@@ -74,10 +97,11 @@ def test(self):
     static_folder='static',
     static_url_path='/static/test_plugin')
 
+
 ml = MenuLink(
     category='Test Plugin',
-    name='Test Menu Link',
-    url='https://airflow.incubator.apache.org/')
+    name="Test Menu Link",
+    url="https://airflow.incubator.apache.org/";)
 
 
 # Defining the plugin class
@@ -91,3 +115,5 @@ class AirflowTestPlugin(AirflowPlugin):
     admin_views = [v]
     flask_blueprints = [bp]
     menu_links = [ml]
+    appbuilder_views = [v_appbuilder_package]
+    appbuilder_menu_items = [appbuilder_mitem]
diff --git a/tests/plugins_manager.py b/tests/plugins_manager.py
index 9f939c37a0..eeb6494100 100644
--- a/tests/plugins_manager.py
+++ b/tests/plugins_manager.py
@@ -27,11 +27,13 @@
 from flask.blueprints import Blueprint
 from flask_admin.menu import MenuLink, MenuView
 
+from airflow.configuration import conf
 from airflow.hooks.base_hook import BaseHook
 from airflow.models import BaseOperator
 from airflow.sensors.base_sensor_operator import BaseSensorOperator
 from airflow.executors.base_executor import BaseExecutor
 from airflow.www.app import cached_app
+from airflow.www_rbac import app as application
 
 
 class PluginsTest(unittest.TestCase):
@@ -83,3 +85,42 @@ def test_menu_links(self):
         [menu_link] = [ml for ml in category.get_children()
                        if isinstance(ml, MenuLink)]
         self.assertEqual('Test Menu Link', menu_link.name)
+
+
+class PluginsTestRBAC(unittest.TestCase):
+    def setUp(self):
+        conf.load_test_config()
+        self.app, self.appbuilder = application.create_app(testing=True)
+
+    def test_flaskappbuilder_views(self):
+        from tests.plugins.test_plugin import v_appbuilder_package
+        appbuilder_class_name = 
str(v_appbuilder_package['view'].__class__.__name__)
+        plugin_views = [view for view in self.appbuilder.baseviews
+                        if view.blueprint.name == appbuilder_class_name]
+
+        self.assertTrue(len(plugin_views) == 1)
+
+        # view should have a menu item matching category of 
v_appbuilder_package
+        links = [menu_item for menu_item in self.appbuilder.menu.menu
+                 if menu_item.name == v_appbuilder_package['category']]
+
+        self.assertTrue(len(links) == 1)
+
+        # menu link should also have a link matching the name of the package.
+        link = links[0]
+        self.assertEqual(link.name, v_appbuilder_package['category'])
+        self.assertEqual(link.childs[0].name, v_appbuilder_package['name'])
+
+    def test_flaskappbuilder_menu_links(self):
+        from tests.plugins.test_plugin import appbuilder_mitem
+
+        # menu item should exist matching appbuilder_mitem
+        links = [menu_item for menu_item in self.appbuilder.menu.menu
+                 if menu_item.name == appbuilder_mitem['category']]
+
+        self.assertTrue(len(links) == 1)
+
+        # menu link should also have a link matching the name of the package.
+        link = links[0]
+        self.assertEqual(link.name, appbuilder_mitem['category'])
+        self.assertEqual(link.childs[0].name, appbuilder_mitem['name'])


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


> RBAC app doesn't integrate plugins (blueprints etc)
> ---------------------------------------------------
>
>                 Key: AIRFLOW-2744
>                 URL: https://issues.apache.org/jira/browse/AIRFLOW-2744
>             Project: Apache Airflow
>          Issue Type: Bug
>          Components: webapp, webserver
>    Affects Versions: 2.0.0
>            Reporter: David Dossett
>            Priority: Major
>             Fix For: 2.0.0, 1.10.1
>
>
> In the current 1.10.0rc tag, the new RBAC app doesn't integrate any plugins 
> created by a user extending Airflow. In the old www/app.py you had the 
> [integrate_plugins|https://github.com/apache/incubator-airflow/blob/f1083cbada337731ed0b7e27b09eee7a26c8189a/airflow/www/app.py#L126]
>  function. But currently the 
> [www_rbac/app.py|https://github.com/apache/incubator-airflow/blob/f1083cbada337731ed0b7e27b09eee7a26c8189a/airflow/www_rbac/app.py]
>  doesn't pull in any plugins from the plugin_manager. So nothing you do to 
> extend Airflow's webapp will work.
> I think adding the code for registering the blueprints and menu links is a 
> pretty simple fix. I'm not sure how the FAB system is handling the same 
> functionality as Flask-Admin views though.
>  



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

Reply via email to