You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by "vincbeck (via GitHub)" <gi...@apache.org> on 2024/11/28 21:47:24 UTC

[PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

vincbeck opened a new pull request, #44464:
URL: https://github.com/apache/airflow/pull/44464

   To support Airflow 2.x plugins in Airflow 3 the goal is to embed a minimal version of Airflow 2 in fast api using [WSGIMiddleware](https://fastapi.tiangolo.com/advanced/wsgi/). The Airflow 2.x plugins will run in this application as they are running today in Airflow 2. `WSGIMiddleware` allows running a WSGI in the fast api application. This Flask application will be accessible at the path `/pluginsv2`.
   
   The plan is to:
   1. (This PR) Embed Airflow 2 in the fast api application so that Airflow 2.x plugins will be available in Airflow 3. The Flask application will then be available in Flask webserver and FastApi runner
   2. When the new Airflow 3 UI becomes the default UI and no fallback to the old UI is possible, strip out the Flask application to everything besides core elements needed by the plugins (these core elements are not identified yet, this will need to be done)
   3. Move what is remaining of the Flask code from core Airflow to fab provider. The idea is, if you do not have any Airflow 2.x plugin defined in your environment, you should not have Flask as dependency. On the opposite, if you do have some Airflow 2.x plugins in your environment, fab provider will have to be installed and all the code needed to run the Flask application will be in this provider.
   
   The experience for the user will not be ideal because these plugins use the Airflow 2 UI, therefore the UI experience will be inconsistent between the Airflow pages (Airflow 3 UI) and the plugins (Airflow 2 UI). A warning message will be added in the embedded Flask application to let the user knows about this weird experience but also to recommend them to migrate their plugins to Airflow 3 plugins.
   
   <!--
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at
   
      http://www.apache.org/licenses/LICENSE-2.0
   
    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
    -->
   
   <!--
   Thank you for contributing! Please make sure that your code changes
   are covered with tests. And in case of new features or big changes
   remember to adjust the documentation.
   
   Feel free to ping committers for the review!
   
   In case of an existing issue, reference it using one of the following:
   
   closes: #ISSUE
   related: #ISSUE
   
   How to write a good git commit message:
   http://chris.beams.io/posts/git-commit/
   -->
   
   
   
   <!-- Please keep an empty line above the dashes. -->
   ---
   **^ Add meaningful description above**
   Read the **[Pull Request Guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#pull-request-guidelines)** for more information.
   In case of fundamental code changes, an Airflow Improvement Proposal ([AIP](https://cwiki.apache.org/confluence/display/AIRFLOW/Airflow+Improvement+Proposals)) is needed.
   In case of a new dependency, check compliance with the [ASF 3rd Party License Policy](https://www.apache.org/legal/resolved.html#category-x).
   In case of backwards incompatible changes please leave a note in a newsfragment file, named `{pr_number}.significant.rst` or `{issue_number}.significant.rst`, in [newsfragments](https://github.com/apache/airflow/tree/main/newsfragments).
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on PR #44464:
URL: https://github.com/apache/airflow/pull/44464#issuecomment-2506783732

   @jedcunningham 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on code in PR #44464:
URL: https://github.com/apache/airflow/pull/44464#discussion_r1873731114


##########
providers/src/airflow/providers/fab/www/templates/swagger-ui/index.j2:
##########
@@ -0,0 +1,87 @@
+{#

Review Comment:
   Good point! I removed it and did a bit of cleanup in the `webpack.config.js`. I am pretty sure we could remove more stuff but that's something we can do later



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on code in PR #44464:
URL: https://github.com/apache/airflow/pull/44464#discussion_r1869877531


##########
providers/src/airflow/providers/fab/www/static/js/toggle_theme.js:
##########
@@ -0,0 +1,46 @@
+/*!

Review Comment:
   Sounds good



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "jedcunningham (via GitHub)" <gi...@apache.org>.
jedcunningham commented on PR #44464:
URL: https://github.com/apache/airflow/pull/44464#issuecomment-2523936089

   Whoo! Nice!


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck merged PR #44464:
URL: https://github.com/apache/airflow/pull/44464


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on code in PR #44464:
URL: https://github.com/apache/airflow/pull/44464#discussion_r1869906202


##########
providers/src/airflow/providers/fab/www/package.json:
##########
@@ -0,0 +1,156 @@
+{
+  "name": "airflow-www",
+  "version": "1.0.0",
+  "description": "Apache Airflow is a platform to programmatically author, schedule and monitor workflows.",
+  "scripts": {

Review Comment:
   It should be done in a separate PR. I created a draft task in the project board: https://github.com/orgs/apache/projects/438



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on code in PR #44464:
URL: https://github.com/apache/airflow/pull/44464#discussion_r1869906202


##########
providers/src/airflow/providers/fab/www/package.json:
##########
@@ -0,0 +1,156 @@
+{
+  "name": "airflow-www",
+  "version": "1.0.0",
+  "description": "Apache Airflow is a platform to programmatically author, schedule and monitor workflows.",
+  "scripts": {

Review Comment:
   It should be done as a separate PR. I created a draft task in the project board: https://github.com/orgs/apache/projects/438



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "pierrejeambrun (via GitHub)" <gi...@apache.org>.
pierrejeambrun commented on code in PR #44464:
URL: https://github.com/apache/airflow/pull/44464#discussion_r1871476799


##########
airflow/api_fastapi/core_api/app.py:
##########
@@ -98,6 +100,41 @@ def init_plugins(app: FastAPI) -> None:
         app.mount(url_prefix, subapp)
 
 
+def init_flask_plugins(app: FastAPI) -> None:
+    """Integrate Flask plugins (plugins from Airflow 2)."""
+    from airflow import plugins_manager
+
+    plugins_manager.initialize_flask_plugins()
+
+    # If no Airflow 2.x plugin is in the environment, no need to go further
+    if (
+        not plugins_manager.flask_blueprints
+        and not plugins_manager.flask_appbuilder_views
+        and not plugins_manager.flask_appbuilder_menu_links
+    ):
+        return
+
+    from fastapi.middleware.wsgi import WSGIMiddleware
+
+    try:
+        from airflow.providers.fab.www.app import create_app
+    except ImportError:
+        raise AirflowException(
+            "Some Airflow 2 plugins have been detected in your environment. "
+            "To run them with Airflow 3, you must install fab provider in your Airflow environment."
+        )
+
+    warnings.warn(
+        "You have a plugin that is using a FAB view or Flask Blueprint, which was used for the Airflow 2 UI,"
+        "and is now deprecated. Please update your plugin to be compatible with the Airflow 3 UI.",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+
+    flask_app = create_app()
+    app.mount("/pluginsv2", WSGIMiddleware(flask_app))

Review Comment:
   Nice! I didn't know that we could mount a flask app in fastapi that easily.
   
   Really takes a big pain off!



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on code in PR #44464:
URL: https://github.com/apache/airflow/pull/44464#discussion_r1868339019


##########
providers/src/airflow/providers/fab/www/extensions/init_appbuilder.py:
##########
@@ -0,0 +1,580 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# mypy: disable-error-code=var-annotated
+from __future__ import annotations
+
+import logging
+from functools import reduce
+from typing import TYPE_CHECKING
+
+from flask import Blueprint, current_app, url_for
+from flask_appbuilder import __version__
+from flask_appbuilder.babel.manager import BabelManager
+from flask_appbuilder.const import (
+    LOGMSG_ERR_FAB_ADD_PERMISSION_MENU,
+    LOGMSG_ERR_FAB_ADD_PERMISSION_VIEW,
+    LOGMSG_ERR_FAB_ADDON_IMPORT,
+    LOGMSG_ERR_FAB_ADDON_PROCESS,
+    LOGMSG_INF_FAB_ADD_VIEW,
+    LOGMSG_INF_FAB_ADDON_ADDED,
+    LOGMSG_WAR_FAB_VIEW_EXISTS,
+)
+from flask_appbuilder.filters import TemplateFilters
+from flask_appbuilder.menu import Menu
+from flask_appbuilder.views import IndexView
+
+from airflow import settings
+from airflow.configuration import conf
+from airflow.www.extensions.init_auth_manager import init_auth_manager
+
+if TYPE_CHECKING:
+    from flask import Flask
+    from flask_appbuilder import BaseView
+    from flask_appbuilder.security.manager import BaseSecurityManager
+    from sqlalchemy.orm import Session
+
+
+# This product contains a modified portion of 'Flask App Builder' developed by Daniel Vaz Gaspar.
+# (https://github.com/dpgaspar/Flask-AppBuilder).
+# Copyright 2013, Daniel Vaz Gaspar
+# This module contains code imported from FlaskAppbuilder, so lets use _its_ logger name
+log = logging.getLogger("flask_appbuilder.base")
+
+
+def dynamic_class_import(class_path):
+    """
+    Will dynamically import a class from a string path.
+
+    :param class_path: string with class path
+    :return: class
+    """
+    # Split first occurrence of path
+    try:
+        tmp = class_path.split(".")
+        module_path = ".".join(tmp[0:-1])
+        package = __import__(module_path)
+        return reduce(getattr, tmp[1:], package)
+    except Exception as e:
+        log.exception(e)
+        log.error(LOGMSG_ERR_FAB_ADDON_IMPORT, class_path, e)
+
+
+class AirflowAppBuilder:
+    """
+    This is the base class for all the framework.
+
+    This is where you will register all your views
+    and create the menu structure.
+    Will hold your flask app object, all your views, and security classes.
+    Initialize your application like this for SQLAlchemy::
+        from flask import Flask
+        from flask_appbuilder import SQLA, AppBuilder
+        app = Flask(__name__)
+        app.config.from_object('config')
+        db = SQLA(app)
+        appbuilder = AppBuilder(app, db.session)
+    When using MongoEngine::
+        from flask import Flask
+        from flask_appbuilder import AppBuilder
+        from flask_appbuilder.security.mongoengine.manager import SecurityManager
+        from flask_mongoengine import MongoEngine
+        app = Flask(__name__)
+        app.config.from_object('config')
+        dbmongo = MongoEngine(app)
+        appbuilder = AppBuilder(app)
+    You can also create everything as an application factory.
+    """
+
+    baseviews: list[BaseView | Session] = []
+    # Flask app
+    app = None
+    # Database Session
+    session = None
+    # Security Manager Class
+    sm: BaseSecurityManager
+    # Babel Manager Class
+    bm = None
+    # dict with addon name has key and instantiated class has value
+    addon_managers: dict
+    # temporary list that hold addon_managers config key
+    _addon_managers: list
+
+    menu = None
+    indexview = None
+
+    static_folder = None
+    static_url_path = None
+
+    template_filters = None
+
+    def __init__(
+        self,
+        app=None,
+        session: Session | None = None,
+        menu=None,
+        indexview=None,
+        base_template="airflow/main.html",
+        static_folder="static/appbuilder",
+        static_url_path="/appbuilder",
+    ):
+        """
+        App-builder constructor.
+
+        :param app:
+            The flask app object
+        :param session:
+            The SQLAlchemy session object
+        :param menu:
+            optional, a previous constructed menu
+        :param indexview:
+            optional, your customized indexview
+        :param static_folder:
+            optional, your override for the global static folder
+        :param static_url_path:
+            optional, your override for the global static url path
+        """
+        from airflow.providers_manager import ProvidersManager
+
+        providers_manager = ProvidersManager()
+        providers_manager.initialize_providers_configuration()
+        self.baseviews = []
+        self._addon_managers = []
+        self.addon_managers = {}
+        self.menu = menu
+        self.base_template = base_template
+        self.indexview = indexview
+        self.static_folder = static_folder
+        self.static_url_path = static_url_path
+        self.app = app
+        self.update_perms = conf.getboolean("fab", "UPDATE_FAB_PERMS")
+        self.auth_rate_limited = conf.getboolean("fab", "AUTH_RATE_LIMITED")
+        self.auth_rate_limit = conf.get("fab", "AUTH_RATE_LIMIT")
+        if app is not None:
+            self.init_app(app, session)
+
+    def init_app(self, app, session):
+        """
+        Will initialize the Flask app, supporting the app factory pattern.
+
+        :param app:
+        :param session: The SQLAlchemy session
+        """
+        app.config.setdefault("APP_NAME", "F.A.B.")
+        app.config.setdefault("APP_THEME", "")
+        app.config.setdefault("APP_ICON", "")
+        app.config.setdefault("LANGUAGES", {"en": {"flag": "gb", "name": "English"}})
+        app.config.setdefault("ADDON_MANAGERS", [])
+        app.config.setdefault("RATELIMIT_ENABLED", self.auth_rate_limited)
+        app.config.setdefault("FAB_BASE_TEMPLATE", self.base_template)
+        app.config.setdefault("FAB_STATIC_FOLDER", self.static_folder)
+        app.config.setdefault("FAB_STATIC_URL_PATH", self.static_url_path)
+        app.config.setdefault("AUTH_RATE_LIMITED", self.auth_rate_limited)
+        app.config.setdefault("AUTH_RATE_LIMIT", self.auth_rate_limit)
+
+        self.app = app
+
+        self.base_template = app.config.get("FAB_BASE_TEMPLATE", self.base_template)
+        self.static_folder = app.config.get("FAB_STATIC_FOLDER", self.static_folder)
+        self.static_url_path = app.config.get("FAB_STATIC_URL_PATH", self.static_url_path)
+        _index_view = app.config.get("FAB_INDEX_VIEW", None)
+        if _index_view is not None:
+            self.indexview = dynamic_class_import(_index_view)
+        else:
+            self.indexview = self.indexview or IndexView
+        _menu = app.config.get("FAB_MENU", None)
+        if _menu is not None:
+            self.menu = dynamic_class_import(_menu)
+        else:
+            self.menu = self.menu or Menu()
+
+        self._addon_managers = app.config["ADDON_MANAGERS"]
+        self.session = session
+        auth_manager = init_auth_manager(self)
+        self.sm = auth_manager.security_manager
+        self.bm = BabelManager(self)
+        self._add_global_static()
+        self._add_global_filters()
+        app.before_request(self.sm.before_request)
+        self._add_admin_views()
+        self._add_addon_views()
+        self._init_extension(app)
+
+    def _init_extension(self, app):
+        app.appbuilder = self
+        if not hasattr(app, "extensions"):
+            app.extensions = {}
+        app.extensions["appbuilder"] = self
+
+    @property
+    def get_app(self):
+        """
+        Get current or configured flask app.
+
+        :return: Flask App
+        """
+        if self.app:
+            return self.app
+        else:
+            return current_app
+
+    @property
+    def get_session(self):
+        """
+        Get the current sqlalchemy session.
+
+        :return: SQLAlchemy Session
+        """
+        return self.session
+
+    @property
+    def app_name(self):
+        """
+        Get the App name.
+
+        :return: String with app name
+        """
+        return self.get_app.config["APP_NAME"]
+
+    @property
+    def app_theme(self):
+        """
+        Get the App theme name.
+
+        :return: String app theme name
+        """
+        return self.get_app.config["APP_THEME"]
+
+    @property
+    def app_icon(self):
+        """
+        Get the App icon location.
+
+        :return: String with relative app icon location
+        """
+        return self.get_app.config["APP_ICON"]
+
+    @property
+    def languages(self):
+        return self.get_app.config["LANGUAGES"]
+
+    @property
+    def version(self):
+        """
+        Get the current F.A.B. version.
+
+        :return: String with the current F.A.B. version
+        """
+        return __version__
+
+    def _add_global_filters(self):
+        self.template_filters = TemplateFilters(self.get_app, self.sm)
+
+    def _add_global_static(self):
+        bp = Blueprint(
+            "appbuilder",
+            "flask_appbuilder.base",
+            url_prefix="/static",
+            template_folder="templates",
+            static_folder=self.static_folder,
+            static_url_path=self.static_url_path,
+        )
+        self.get_app.register_blueprint(bp)
+
+    def _add_admin_views(self):
+        """Register indexview, utilview (back function), babel views and Security views."""
+        self.indexview = self._check_and_init(self.indexview)
+        self.add_view_no_menu(self.indexview)
+
+    def _add_addon_views(self):
+        """Register declared addons."""
+        for addon in self._addon_managers:
+            addon_class = dynamic_class_import(addon)
+            if addon_class:
+                # Instantiate manager with appbuilder (self)
+                addon_class = addon_class(self)
+                try:
+                    addon_class.pre_process()
+                    addon_class.register_views()
+                    addon_class.post_process()
+                    self.addon_managers[addon] = addon_class
+                    log.info(LOGMSG_INF_FAB_ADDON_ADDED, addon)
+                except Exception as e:
+                    log.exception(e)
+                    log.error(LOGMSG_ERR_FAB_ADDON_PROCESS, addon, e)
+
+    def _check_and_init(self, baseview):
+        if hasattr(baseview, "datamodel"):
+            baseview.datamodel.session = self.session
+        if callable(baseview):
+            baseview = baseview()
+        return baseview
+
+    def add_view(
+        self,
+        baseview,
+        name,
+        href="",
+        icon="",
+        label="",
+        category="",
+        category_icon="",
+        category_label="",
+        menu_cond=None,
+    ):
+        """
+        Add your views associated with menus using this method.
+
+        :param baseview:
+            A BaseView type class instantiated or not.
+            This method will instantiate the class for you if needed.
+        :param name:
+            The string name that identifies the menu.
+        :param href:
+            Override the generated href for the menu.
+            You can use an url string or an endpoint name
+            if non provided default_view from view will be set as href.
+        :param icon:
+            Font-Awesome icon name, optional.
+        :param label:
+            The label that will be displayed on the menu,
+            if absent param name will be used
+        :param category:
+            The menu category where the menu will be included,
+            if non provided the view will be accessible as a top menu.
+        :param category_icon:
+            Font-Awesome icon name for the category, optional.
+        :param category_label:
+            The label that will be displayed on the menu,
+            if absent param name will be used
+        :param menu_cond:
+            If a callable, :code:`menu_cond` will be invoked when
+            constructing the menu items. If it returns :code:`True`,
+            then this link will be a part of the menu. Otherwise, it
+            will not be included in the menu items. Defaults to
+            :code:`None`, meaning the item will always be present.
+
+        Examples::
+
+            appbuilder = AppBuilder(app, db)
+            # Register a view, rendering a top menu without icon.
+            appbuilder.add_view(MyModelView(), "My View")
+            # or not instantiated
+            appbuilder.add_view(MyModelView, "My View")
+            # Register a view, a submenu "Other View" from "Other" with a phone icon.
+            appbuilder.add_view(MyOtherModelView, "Other View", icon="fa-phone", category="Others")
+            # Register a view, with category icon and translation.
+            appbuilder.add_view(
+                YetOtherModelView,
+                "Other View",
+                icon="fa-phone",
+                label=_("Other View"),
+                category="Others",
+                category_icon="fa-envelop",
+                category_label=_("Other View"),
+            )
+            # Register a view whose menu item will be conditionally displayed
+            appbuilder.add_view(
+                YourFeatureView,
+                "Your Feature",
+                icon="fa-feature",
+                label=_("Your Feature"),
+                menu_cond=lambda: is_feature_enabled("your-feature"),
+            )
+            # Add a link
+            appbuilder.add_link("google", href="www.google.com", icon="fa-google-plus")
+        """
+        baseview = self._check_and_init(baseview)
+        log.info(LOGMSG_INF_FAB_ADD_VIEW, baseview.__class__.__name__, name)
+
+        if not self._view_exists(baseview):
+            baseview.appbuilder = self
+            self.baseviews.append(baseview)
+            self._process_inner_views()
+            if self.app:
+                self.register_blueprint(baseview)
+                self._add_permission(baseview)
+                self.add_limits(baseview)
+        self.add_link(
+            name=name,
+            href=href,
+            icon=icon,
+            label=label,
+            category=category,
+            category_icon=category_icon,
+            category_label=category_label,
+            baseview=baseview,
+            cond=menu_cond,
+        )
+        return baseview
+
+    def add_link(
+        self,
+        name,
+        href,
+        icon="",
+        label="",
+        category="",
+        category_icon="",
+        category_label="",
+        baseview=None,
+        cond=None,
+    ):
+        """
+        Add your own links to menu using this method.
+
+        :param name:
+            The string name that identifies the menu.
+        :param href:
+            Override the generated href for the menu.
+            You can use an url string or an endpoint name
+        :param icon:
+            Font-Awesome icon name, optional.
+        :param label:
+            The label that will be displayed on the menu,
+            if absent param name will be used
+        :param category:
+            The menu category where the menu will be included,
+            if non provided the view will be accessible as a top menu.
+        :param category_icon:
+            Font-Awesome icon name for the category, optional.
+        :param category_label:
+            The label that will be displayed on the menu,
+            if absent param name will be used
+        :param baseview:
+            A BaseView type class instantiated.
+        :param cond:
+            If a callable, :code:`cond` will be invoked when
+            constructing the menu items. If it returns :code:`True`,
+            then this link will be a part of the menu. Otherwise, it
+            will not be included in the menu items. Defaults to
+            :code:`None`, meaning the item will always be present.
+        """
+        self.menu.add_link(
+            name=name,
+            href=href,
+            icon=icon,
+            label=label,
+            category=category,
+            category_icon=category_icon,
+            category_label=category_label,
+            baseview=baseview,
+            cond=cond,
+        )
+        if self.app:
+            self._add_permissions_menu(name)
+            if category:
+                self._add_permissions_menu(category)
+
+    def add_separator(self, category, cond=None):
+        """
+        Add a separator to the menu, you will sequentially create the menu.
+
+        :param category:
+            The menu category where the separator will be included.
+        :param cond:
+            If a callable, :code:`cond` will be invoked when
+            constructing the menu items. If it returns :code:`True`,
+            then this separator will be a part of the menu. Otherwise,
+            it will not be included in the menu items. Defaults to
+            :code:`None`, meaning the separator will always be present.
+        """
+        self.menu.add_separator(category, cond=cond)
+
+    def add_view_no_menu(self, baseview, endpoint=None, static_folder=None):
+        """
+        Add your views without creating a menu.
+
+        :param baseview: A BaseView type class instantiated.
+        """
+        baseview = self._check_and_init(baseview)
+        log.info(LOGMSG_INF_FAB_ADD_VIEW, baseview.__class__.__name__, "")
+
+        if not self._view_exists(baseview):
+            baseview.appbuilder = self
+            self.baseviews.append(baseview)
+            self._process_inner_views()
+            if self.app:
+                self.register_blueprint(baseview, endpoint=endpoint, static_folder=static_folder)
+                self._add_permission(baseview)
+        else:
+            log.warning(LOGMSG_WAR_FAB_VIEW_EXISTS, baseview.__class__.__name__)
+        return baseview
+
+    @property
+    def get_url_for_index(self):
+        # TODO: Return the fast api application homepage

Review Comment:
   TODO: Return the fast api application homepage. When the user click on the Airflow logo on the top left, it should go back to Airflow 3 home page



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on code in PR #44464:
URL: https://github.com/apache/airflow/pull/44464#discussion_r1868305774


##########
providers/src/airflow/providers/fab/www/extensions/init_jinja_globals.py:
##########
@@ -0,0 +1,80 @@
+# Licensed to the Apache Software Foundation (ASF) under one

Review Comment:
   Copy and simplified version of `airflow/www/extensions/init_jinja_globals.py`



##########
providers/src/airflow/providers/fab/www/extensions/init_appbuilder.py:
##########
@@ -0,0 +1,580 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# mypy: disable-error-code=var-annotated
+from __future__ import annotations
+
+import logging
+from functools import reduce
+from typing import TYPE_CHECKING
+
+from flask import Blueprint, current_app, url_for
+from flask_appbuilder import __version__
+from flask_appbuilder.babel.manager import BabelManager
+from flask_appbuilder.const import (
+    LOGMSG_ERR_FAB_ADD_PERMISSION_MENU,
+    LOGMSG_ERR_FAB_ADD_PERMISSION_VIEW,
+    LOGMSG_ERR_FAB_ADDON_IMPORT,
+    LOGMSG_ERR_FAB_ADDON_PROCESS,
+    LOGMSG_INF_FAB_ADD_VIEW,
+    LOGMSG_INF_FAB_ADDON_ADDED,
+    LOGMSG_WAR_FAB_VIEW_EXISTS,
+)
+from flask_appbuilder.filters import TemplateFilters
+from flask_appbuilder.menu import Menu
+from flask_appbuilder.views import IndexView
+
+from airflow import settings
+from airflow.configuration import conf
+from airflow.www.extensions.init_auth_manager import init_auth_manager

Review Comment:
   TODO: move this extension. I have not done it yet since it seems a bit more complicated than the others.



##########
providers/src/airflow/providers/fab/www/static/js/toggle_theme.js:
##########
@@ -0,0 +1,46 @@
+/*!

Review Comment:
   Copy of `airflow/www/static/js/toggle_theme.js`



##########
providers/src/airflow/providers/fab/www/app.py:
##########
@@ -0,0 +1,87 @@
+#

Review Comment:
   I copied this file from `airflow/www/app.py` and removed what seemed to me unnecessary



##########
providers/src/airflow/providers/fab/www/extensions/init_manifest_files.py:
##########
@@ -0,0 +1,61 @@
+#

Review Comment:
   Copy of `airflow/www/extensions/init_manifest_files.py`



##########
providers/src/airflow/providers/fab/www/extensions/init_appbuilder.py:
##########
@@ -0,0 +1,580 @@
+# Licensed to the Apache Software Foundation (ASF) under one

Review Comment:
   Copy of `airflow/www/extensions/init_appbuilder.py` with some methods removed



##########
providers/src/airflow/providers/fab/www/package.json:
##########
@@ -0,0 +1,156 @@
+{
+  "name": "airflow-www",
+  "version": "1.0.0",
+  "description": "Apache Airflow is a platform to programmatically author, schedule and monitor workflows.",
+  "scripts": {
+    "test": "jest",
+    "dev": "NODE_ENV=development webpack --watch --progress --devtool eval-cheap-source-map --mode development",
+    "prod": "NODE_ENV=production node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --mode production --progress",
+    "build": "NODE_ENV=production webpack --progress --mode production",
+    "lint": "eslint --ignore-path=.eslintignore --max-warnings=0 --ext .js,.jsx,.ts,.tsx . && tsc",
+    "lint:fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx,.ts,.tsx . && tsc",
+    "format": "yarn prettier --write .",
+    "generate-api-types": "npx openapi-typescript \"../api_connexion/openapi/v1.yaml\" --output static/js/types/api-generated.ts && node alias-rest-types.js static/js/types/api-generated.ts"
+  },
+  "author": "Apache",
+  "license": "Apache-2.0",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/apache/airflow.git"
+  },
+  "homepage": "https://airflow.apache.org/",
+  "keywords": [
+    "big",
+    "data",
+    "workflow",
+    "airflow",
+    "d3",
+    "nerds",
+    "database",
+    "flask"
+  ],
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not op_mini all"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
+  },
+  "devDependencies": {

Review Comment:
   Should we try to reduce the number of dependencies? Not exactly sure which ones do the plugins need



##########
providers/src/airflow/providers/fab/www/static/js/main.js:
##########
@@ -0,0 +1,324 @@
+/*!

Review Comment:
   Copy of `airflow/www/static/js/main.js`



##########
providers/src/airflow/providers/fab/www/extensions/init_views.py:
##########
@@ -0,0 +1,67 @@
+# Licensed to the Apache Software Foundation (ASF) under one

Review Comment:
   Copy of `airflow/www/extensions/init_views.py`. I kept only `init_plugins` and `init_error_handlers` methods



##########
providers/src/airflow/providers/fab/www/static/css/loading-dots.css:
##########
@@ -0,0 +1,60 @@
+/*!

Review Comment:
   Copy of `airflow/www/static/css/loading-dots.css`



##########
providers/src/airflow/providers/fab/www/templates/airflow/main.html:
##########
@@ -0,0 +1,85 @@
+{#

Review Comment:
   Copy of `airflow/www/templates/airflow/main.html`. I removed unnecessary blocks



##########
providers/src/airflow/providers/fab/www/static/css/main.css:
##########
@@ -0,0 +1,676 @@
+/*!

Review Comment:
   Copy of `airflow/www/static/css/main.css`



##########
providers/src/airflow/providers/fab/www/static/css/bootstrap-theme.css:
##########
@@ -0,0 +1,6215 @@
+/*!

Review Comment:
   Copy of `airflow/www/static/css/bootstrap-theme.css`



##########
providers/src/airflow/providers/fab/www/package.json:
##########
@@ -0,0 +1,156 @@
+{
+  "name": "airflow-www",
+  "version": "1.0.0",
+  "description": "Apache Airflow is a platform to programmatically author, schedule and monitor workflows.",
+  "scripts": {

Review Comment:
   The `build` script needs to be run to generate assets in `providers/src/airflow/providers/fab/www/static/dist`. I guess I need to modify the script `scripts/ci/pre_commit/compile_www_assets.py` to also generate assets in the fab provider? Until Airflow 3 UI will become the default UI in Airflow we'll generate twice the assets (one in core Airflow, one in Fab provider). Once the transition to the new UI done, we will only do it in Fab provider?



##########
providers/src/airflow/providers/fab/www/static/js/datetime_utils.js:
##########
@@ -0,0 +1,134 @@
+/*!

Review Comment:
   Copy of `airflow/www/static/js/datetime_utils.js`



##########
providers/src/airflow/providers/fab/www/extensions/init_security.py:
##########
@@ -0,0 +1,42 @@
+# Licensed to the Apache Software Foundation (ASF) under one

Review Comment:
   Copy of `airflow/www/extensions/init_security.py`. I kept only `init_xframe_protection` method



##########
providers/src/airflow/providers/fab/www/templates/airflow/_messages.html:
##########
@@ -0,0 +1,30 @@
+{#

Review Comment:
   Copy of `airflow/www/templates/airflow/_messages.html`



##########
providers/src/airflow/providers/fab/www/templates/appbuilder/navbar_menu.html:
##########
@@ -0,0 +1,60 @@
+{#

Review Comment:
   Copy of `airflow/www/templates/appbuilder/navbar_menu.html`



##########
providers/src/airflow/providers/fab/www/templates/appbuilder/navbar.html:
##########
@@ -0,0 +1,53 @@
+{#

Review Comment:
   Copy of `airflow/www/templates/appbuilder/navbar.html`. I removed the right part of the header (containing user information). But maybe we want to keep it?



##########
providers/src/airflow/providers/fab/www/static/css/material-icons.css:
##########
@@ -0,0 +1,84 @@
+/*!

Review Comment:
   Copy of `airflow/www/static/css/material-icons.css`



##########
providers/src/airflow/providers/fab/www/templates/airflow/traceback.html:
##########
@@ -0,0 +1,57 @@
+{#

Review Comment:
   Copy of `airflow/www/templates/airflow/traceback.html`



##########
providers/src/airflow/providers/fab/www/templates/swagger-ui/index.j2:
##########
@@ -0,0 +1,87 @@
+{#

Review Comment:
   Copy of `airflow/www/templates/swagger-ui/index.j2`



##########
providers/src/airflow/providers/fab/www/webpack.config.js:
##########
@@ -0,0 +1,270 @@
+/*!

Review Comment:
   Copy of `airflow/www/webpack.config.js`. I removed all the assets specific to pages. These assets are no longer necessary.



##########
providers/src/airflow/providers/fab/www/templates/airflow/error.html:
##########
@@ -0,0 +1,35 @@
+{#

Review Comment:
   Copy of `airflow/www/templates/airflow/error.html`



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on PR #44464:
URL: https://github.com/apache/airflow/pull/44464#issuecomment-2520498376

   > Can we also reuse that same application to do the flask auth server ? (When people are use FABAuthManager and we need to reach for the flask webserver) ?
   
   I was wondering the same and I think we should do that


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on code in PR #44464:
URL: https://github.com/apache/airflow/pull/44464#discussion_r1869890180


##########
providers/src/airflow/providers/fab/www/extensions/init_views.py:
##########
@@ -0,0 +1,67 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from flask import Flask
+
+log = logging.getLogger(__name__)
+
+
+def init_plugins(app):
+    """Integrate Flask and FAB with plugins."""
+    from airflow import plugins_manager
+
+    plugins_manager.initialize_flask_plugins()
+
+    appbuilder = app.appbuilder
+
+    for view in plugins_manager.flask_appbuilder_views:

Review Comment:
   Good point. I added one in the function `init_flask_plugins` in `airflow/api_fastapi/core_api/app.py`. I think it makes more sense. WDYT?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "jedcunningham (via GitHub)" <gi...@apache.org>.
jedcunningham commented on code in PR #44464:
URL: https://github.com/apache/airflow/pull/44464#discussion_r1868772181


##########
providers/src/airflow/providers/fab/www/package.json:
##########
@@ -0,0 +1,156 @@
+{
+  "name": "airflow-www",
+  "version": "1.0.0",
+  "description": "Apache Airflow is a platform to programmatically author, schedule and monitor workflows.",
+  "scripts": {

Review Comment:
   Yep, sounds good.



##########
providers/src/airflow/providers/fab/www/extensions/init_appbuilder.py:
##########
@@ -0,0 +1,580 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# mypy: disable-error-code=var-annotated
+from __future__ import annotations
+
+import logging
+from functools import reduce
+from typing import TYPE_CHECKING
+
+from flask import Blueprint, current_app, url_for
+from flask_appbuilder import __version__
+from flask_appbuilder.babel.manager import BabelManager
+from flask_appbuilder.const import (
+    LOGMSG_ERR_FAB_ADD_PERMISSION_MENU,
+    LOGMSG_ERR_FAB_ADD_PERMISSION_VIEW,
+    LOGMSG_ERR_FAB_ADDON_IMPORT,
+    LOGMSG_ERR_FAB_ADDON_PROCESS,
+    LOGMSG_INF_FAB_ADD_VIEW,
+    LOGMSG_INF_FAB_ADDON_ADDED,
+    LOGMSG_WAR_FAB_VIEW_EXISTS,
+)
+from flask_appbuilder.filters import TemplateFilters
+from flask_appbuilder.menu import Menu
+from flask_appbuilder.views import IndexView
+
+from airflow import settings
+from airflow.configuration import conf
+from airflow.www.extensions.init_auth_manager import init_auth_manager

Review Comment:
   Might be worth leaving a comment in the file itself :)



##########
providers/src/airflow/providers/fab/www/package.json:
##########
@@ -0,0 +1,156 @@
+{
+  "name": "airflow-www",
+  "version": "1.0.0",
+  "description": "Apache Airflow is a platform to programmatically author, schedule and monitor workflows.",
+  "scripts": {
+    "test": "jest",
+    "dev": "NODE_ENV=development webpack --watch --progress --devtool eval-cheap-source-map --mode development",
+    "prod": "NODE_ENV=production node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --mode production --progress",
+    "build": "NODE_ENV=production webpack --progress --mode production",
+    "lint": "eslint --ignore-path=.eslintignore --max-warnings=0 --ext .js,.jsx,.ts,.tsx . && tsc",
+    "lint:fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx,.ts,.tsx . && tsc",
+    "format": "yarn prettier --write .",
+    "generate-api-types": "npx openapi-typescript \"../api_connexion/openapi/v1.yaml\" --output static/js/types/api-generated.ts && node alias-rest-types.js static/js/types/api-generated.ts"
+  },
+  "author": "Apache",
+  "license": "Apache-2.0",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/apache/airflow.git"
+  },
+  "homepage": "https://airflow.apache.org/",
+  "keywords": [
+    "big",
+    "data",
+    "workflow",
+    "airflow",
+    "d3",
+    "nerds",
+    "database",
+    "flask"
+  ],
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not op_mini all"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
+  },
+  "devDependencies": {

Review Comment:
   @bbovenzi may be able to help us here. I'd imagine we can get rid of a lot of these.



##########
providers/src/airflow/providers/fab/www/templates/airflow/main.html:
##########
@@ -0,0 +1,85 @@
+{#
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+#}
+
+{% extends 'appbuilder/baselayout.html' %}
+{% from 'airflow/_messages.html' import show_message %}
+
+{% block page_title -%}
+  Airflow - Airflow 2 plugins compatibility view
+{% endblock %}
+
+{% block head_css %}
+  {{ super() }}
+
+  {% if not appbuilder.app_theme %}
+    {# airflowDefaultTheme.css file contains the styles from local bootstrap-theme.css #}
+    <link rel="stylesheet" type="text/css" href="{{ url_for_asset('airflowDefaultTheme.css') }}">
+  {% endif %}
+  <link rel="stylesheet" type="text/css" href="{{ url_for_asset('materialIcons.css') }}">
+  <link rel="stylesheet" type="text/css" href="{{ url_for_asset('main.css') }}">
+  <link rel="stylesheet" type="text/css" href="{{ url_for_asset('loadingDots.css') }}">
+  <link rel="stylesheet" type="text/css" href="{{ url_for_asset('jquery-ui.min.css') }}">
+  <style type="text/css">
+    {% for state, state_color in state_color_mapping.items() %}
+      span.{{state}} {
+        background-color: {{state_color}};
+      }
+    {% endfor %}
+    .navbar-nav > li > a {
+      color: {{ navbar_text_color }};
+    }
+    .navbar-nav > li > a:hover {
+      background-color:  {{ navbar_hover_color }};
+      color: {{ navbar_text_hover_color }};
+    }
+  </style>
+  <link rel="icon" type="image/png" href="{{ url_for('static', filename='pin_32.png') }}">
+{% endblock %}
+
+{% block messages %}
+  {% call show_message(category='warning', dismissible=false) %}
+    <p>
+      This is a limited version of an Airflow 2 environment. The reason why you are using this environment is that
+      you are accessing a page defined in an Airflow plugin only compatible with Airflow 2.
+      This is also why the UI is different from the rest of your Airflow environment.
+    </p>
+    <p>
+      To have the plugin be integrated with the rest of your environment,
+      please update your plugin to make it compatible with Airflow 3.

Review Comment:
   ```suggestion
         You have a plugin that is using a FAB view or Flask Blueprint, which was used for the Airflow 2 UI, and is now
         deprecated. Please update your plugin to be compatible with the Airflow 3 UI.
   ```
   
   Maybe something like this?
   
   We probably need to revisit anyways, because we likely need to leave this UI around for the FAB auth manager too - we we may need this behind a conditional on plugin stuff. But this is a problem for later :)



##########
providers/src/airflow/providers/fab/www/templates/appbuilder/navbar.html:
##########
@@ -0,0 +1,53 @@
+{#

Review Comment:
   Keeping it simple sounds good to me.



##########
providers/src/airflow/providers/fab/www/static/js/toggle_theme.js:
##########
@@ -0,0 +1,46 @@
+/*!

Review Comment:
   I think we can drop support for dark mode. It won't be consistent with AF3 anyways.



##########
providers/src/airflow/providers/fab/www/extensions/init_views.py:
##########
@@ -0,0 +1,67 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import logging
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from flask import Flask
+
+log = logging.getLogger(__name__)
+
+
+def init_plugins(app):
+    """Integrate Flask and FAB with plugins."""
+    from airflow import plugins_manager
+
+    plugins_manager.initialize_flask_plugins()
+
+    appbuilder = app.appbuilder
+
+    for view in plugins_manager.flask_appbuilder_views:

Review Comment:
   Somewhere in here we should issue a deprecation warning. In `init_plugins` is probably the right place. Fine with a follow up as long as we drop a TODO.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on code in PR #44464:
URL: https://github.com/apache/airflow/pull/44464#discussion_r1868337623


##########
providers/src/airflow/providers/fab/www/templates/airflow/main.html:
##########
@@ -0,0 +1,85 @@
+{#
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+#}
+
+{% extends 'appbuilder/baselayout.html' %}
+{% from 'airflow/_messages.html' import show_message %}
+
+{% block page_title -%}
+  Airflow - Airflow 2 plugins compatibility view
+{% endblock %}
+
+{% block head_css %}
+  {{ super() }}
+
+  {% if not appbuilder.app_theme %}
+    {# airflowDefaultTheme.css file contains the styles from local bootstrap-theme.css #}
+    <link rel="stylesheet" type="text/css" href="{{ url_for_asset('airflowDefaultTheme.css') }}">
+  {% endif %}
+  <link rel="stylesheet" type="text/css" href="{{ url_for_asset('materialIcons.css') }}">
+  <link rel="stylesheet" type="text/css" href="{{ url_for_asset('main.css') }}">
+  <link rel="stylesheet" type="text/css" href="{{ url_for_asset('loadingDots.css') }}">
+  <link rel="stylesheet" type="text/css" href="{{ url_for_asset('jquery-ui.min.css') }}">
+  <style type="text/css">
+    {% for state, state_color in state_color_mapping.items() %}
+      span.{{state}} {
+        background-color: {{state_color}};
+      }
+    {% endfor %}
+    .navbar-nav > li > a {
+      color: {{ navbar_text_color }};
+    }
+    .navbar-nav > li > a:hover {
+      background-color:  {{ navbar_hover_color }};
+      color: {{ navbar_text_hover_color }};
+    }
+  </style>
+  <link rel="icon" type="image/png" href="{{ url_for('static', filename='pin_32.png') }}">
+{% endblock %}
+
+{% block messages %}
+  {% call show_message(category='warning', dismissible=false) %}

Review Comment:
   Warning showed to the user whenever they access the Flask application in fast api. This is to warn them why the UI is very different from the Airflow 3 UI. The goal is also to recommend them to migrate their plugins to Airflow 3 plugins. Please feel very free to suggest improvements of the text if you find better :)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on PR #44464:
URL: https://github.com/apache/airflow/pull/44464#issuecomment-2511890607

   > Instead of waiting for #2 to happen, let's just copy/trim whatever minimal FAB stuff we need into the FAB provider now - just leave it completely separate from the old UI. Then we aren't blocked at all.
   
   That sounds good to me. That's a good idea indeed, we can save some time by doing this. Should we do it in a separate PR though? This one sets up the mechanism to embed Airflow 2 in Airflow 3 and the other PR(s) would be copying over the Flask app to Fab provider


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on code in PR #44464:
URL: https://github.com/apache/airflow/pull/44464#discussion_r1869900970


##########
providers/src/airflow/providers/fab/www/extensions/init_appbuilder.py:
##########
@@ -0,0 +1,580 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# mypy: disable-error-code=var-annotated
+from __future__ import annotations
+
+import logging
+from functools import reduce
+from typing import TYPE_CHECKING
+
+from flask import Blueprint, current_app, url_for
+from flask_appbuilder import __version__
+from flask_appbuilder.babel.manager import BabelManager
+from flask_appbuilder.const import (
+    LOGMSG_ERR_FAB_ADD_PERMISSION_MENU,
+    LOGMSG_ERR_FAB_ADD_PERMISSION_VIEW,
+    LOGMSG_ERR_FAB_ADDON_IMPORT,
+    LOGMSG_ERR_FAB_ADDON_PROCESS,
+    LOGMSG_INF_FAB_ADD_VIEW,
+    LOGMSG_INF_FAB_ADDON_ADDED,
+    LOGMSG_WAR_FAB_VIEW_EXISTS,
+)
+from flask_appbuilder.filters import TemplateFilters
+from flask_appbuilder.menu import Menu
+from flask_appbuilder.views import IndexView
+
+from airflow import settings
+from airflow.configuration import conf
+from airflow.www.extensions.init_auth_manager import init_auth_manager
+
+if TYPE_CHECKING:
+    from flask import Flask
+    from flask_appbuilder import BaseView
+    from flask_appbuilder.security.manager import BaseSecurityManager
+    from sqlalchemy.orm import Session
+
+
+# This product contains a modified portion of 'Flask App Builder' developed by Daniel Vaz Gaspar.
+# (https://github.com/dpgaspar/Flask-AppBuilder).
+# Copyright 2013, Daniel Vaz Gaspar
+# This module contains code imported from FlaskAppbuilder, so lets use _its_ logger name
+log = logging.getLogger("flask_appbuilder.base")
+
+
+def dynamic_class_import(class_path):
+    """
+    Will dynamically import a class from a string path.
+
+    :param class_path: string with class path
+    :return: class
+    """
+    # Split first occurrence of path
+    try:
+        tmp = class_path.split(".")
+        module_path = ".".join(tmp[0:-1])
+        package = __import__(module_path)
+        return reduce(getattr, tmp[1:], package)
+    except Exception as e:
+        log.exception(e)
+        log.error(LOGMSG_ERR_FAB_ADDON_IMPORT, class_path, e)
+
+
+class AirflowAppBuilder:
+    """
+    This is the base class for all the framework.
+
+    This is where you will register all your views
+    and create the menu structure.
+    Will hold your flask app object, all your views, and security classes.
+    Initialize your application like this for SQLAlchemy::
+        from flask import Flask
+        from flask_appbuilder import SQLA, AppBuilder
+        app = Flask(__name__)
+        app.config.from_object('config')
+        db = SQLA(app)
+        appbuilder = AppBuilder(app, db.session)
+    When using MongoEngine::
+        from flask import Flask
+        from flask_appbuilder import AppBuilder
+        from flask_appbuilder.security.mongoengine.manager import SecurityManager
+        from flask_mongoengine import MongoEngine
+        app = Flask(__name__)
+        app.config.from_object('config')
+        dbmongo = MongoEngine(app)
+        appbuilder = AppBuilder(app)
+    You can also create everything as an application factory.
+    """
+
+    baseviews: list[BaseView | Session] = []
+    # Flask app
+    app = None
+    # Database Session
+    session = None
+    # Security Manager Class
+    sm: BaseSecurityManager
+    # Babel Manager Class
+    bm = None
+    # dict with addon name has key and instantiated class has value
+    addon_managers: dict
+    # temporary list that hold addon_managers config key
+    _addon_managers: list
+
+    menu = None
+    indexview = None
+
+    static_folder = None
+    static_url_path = None
+
+    template_filters = None
+
+    def __init__(
+        self,
+        app=None,
+        session: Session | None = None,
+        menu=None,
+        indexview=None,
+        base_template="airflow/main.html",
+        static_folder="static/appbuilder",
+        static_url_path="/appbuilder",
+    ):
+        """
+        App-builder constructor.
+
+        :param app:
+            The flask app object
+        :param session:
+            The SQLAlchemy session object
+        :param menu:
+            optional, a previous constructed menu
+        :param indexview:
+            optional, your customized indexview
+        :param static_folder:
+            optional, your override for the global static folder
+        :param static_url_path:
+            optional, your override for the global static url path
+        """
+        from airflow.providers_manager import ProvidersManager
+
+        providers_manager = ProvidersManager()
+        providers_manager.initialize_providers_configuration()
+        self.baseviews = []
+        self._addon_managers = []
+        self.addon_managers = {}
+        self.menu = menu
+        self.base_template = base_template
+        self.indexview = indexview
+        self.static_folder = static_folder
+        self.static_url_path = static_url_path
+        self.app = app
+        self.update_perms = conf.getboolean("fab", "UPDATE_FAB_PERMS")
+        self.auth_rate_limited = conf.getboolean("fab", "AUTH_RATE_LIMITED")
+        self.auth_rate_limit = conf.get("fab", "AUTH_RATE_LIMIT")
+        if app is not None:
+            self.init_app(app, session)
+
+    def init_app(self, app, session):
+        """
+        Will initialize the Flask app, supporting the app factory pattern.
+
+        :param app:
+        :param session: The SQLAlchemy session
+        """
+        app.config.setdefault("APP_NAME", "F.A.B.")
+        app.config.setdefault("APP_THEME", "")
+        app.config.setdefault("APP_ICON", "")
+        app.config.setdefault("LANGUAGES", {"en": {"flag": "gb", "name": "English"}})
+        app.config.setdefault("ADDON_MANAGERS", [])
+        app.config.setdefault("RATELIMIT_ENABLED", self.auth_rate_limited)
+        app.config.setdefault("FAB_BASE_TEMPLATE", self.base_template)
+        app.config.setdefault("FAB_STATIC_FOLDER", self.static_folder)
+        app.config.setdefault("FAB_STATIC_URL_PATH", self.static_url_path)
+        app.config.setdefault("AUTH_RATE_LIMITED", self.auth_rate_limited)
+        app.config.setdefault("AUTH_RATE_LIMIT", self.auth_rate_limit)
+
+        self.app = app
+
+        self.base_template = app.config.get("FAB_BASE_TEMPLATE", self.base_template)
+        self.static_folder = app.config.get("FAB_STATIC_FOLDER", self.static_folder)
+        self.static_url_path = app.config.get("FAB_STATIC_URL_PATH", self.static_url_path)
+        _index_view = app.config.get("FAB_INDEX_VIEW", None)
+        if _index_view is not None:
+            self.indexview = dynamic_class_import(_index_view)
+        else:
+            self.indexview = self.indexview or IndexView
+        _menu = app.config.get("FAB_MENU", None)
+        if _menu is not None:
+            self.menu = dynamic_class_import(_menu)
+        else:
+            self.menu = self.menu or Menu()
+
+        self._addon_managers = app.config["ADDON_MANAGERS"]
+        self.session = session
+        auth_manager = init_auth_manager(self)
+        self.sm = auth_manager.security_manager
+        self.bm = BabelManager(self)
+        self._add_global_static()
+        self._add_global_filters()
+        app.before_request(self.sm.before_request)
+        self._add_admin_views()
+        self._add_addon_views()
+        self._init_extension(app)
+
+    def _init_extension(self, app):
+        app.appbuilder = self
+        if not hasattr(app, "extensions"):
+            app.extensions = {}
+        app.extensions["appbuilder"] = self
+
+    @property
+    def get_app(self):
+        """
+        Get current or configured flask app.
+
+        :return: Flask App
+        """
+        if self.app:
+            return self.app
+        else:
+            return current_app
+
+    @property
+    def get_session(self):
+        """
+        Get the current sqlalchemy session.
+
+        :return: SQLAlchemy Session
+        """
+        return self.session
+
+    @property
+    def app_name(self):
+        """
+        Get the App name.
+
+        :return: String with app name
+        """
+        return self.get_app.config["APP_NAME"]
+
+    @property
+    def app_theme(self):
+        """
+        Get the App theme name.
+
+        :return: String app theme name
+        """
+        return self.get_app.config["APP_THEME"]
+
+    @property
+    def app_icon(self):
+        """
+        Get the App icon location.
+
+        :return: String with relative app icon location
+        """
+        return self.get_app.config["APP_ICON"]
+
+    @property
+    def languages(self):
+        return self.get_app.config["LANGUAGES"]
+
+    @property
+    def version(self):
+        """
+        Get the current F.A.B. version.
+
+        :return: String with the current F.A.B. version
+        """
+        return __version__
+
+    def _add_global_filters(self):
+        self.template_filters = TemplateFilters(self.get_app, self.sm)
+
+    def _add_global_static(self):
+        bp = Blueprint(
+            "appbuilder",
+            "flask_appbuilder.base",
+            url_prefix="/static",
+            template_folder="templates",
+            static_folder=self.static_folder,
+            static_url_path=self.static_url_path,
+        )
+        self.get_app.register_blueprint(bp)
+
+    def _add_admin_views(self):
+        """Register indexview, utilview (back function), babel views and Security views."""
+        self.indexview = self._check_and_init(self.indexview)
+        self.add_view_no_menu(self.indexview)
+
+    def _add_addon_views(self):
+        """Register declared addons."""
+        for addon in self._addon_managers:
+            addon_class = dynamic_class_import(addon)
+            if addon_class:
+                # Instantiate manager with appbuilder (self)
+                addon_class = addon_class(self)
+                try:
+                    addon_class.pre_process()
+                    addon_class.register_views()
+                    addon_class.post_process()
+                    self.addon_managers[addon] = addon_class
+                    log.info(LOGMSG_INF_FAB_ADDON_ADDED, addon)
+                except Exception as e:
+                    log.exception(e)
+                    log.error(LOGMSG_ERR_FAB_ADDON_PROCESS, addon, e)
+
+    def _check_and_init(self, baseview):
+        if hasattr(baseview, "datamodel"):
+            baseview.datamodel.session = self.session
+        if callable(baseview):
+            baseview = baseview()
+        return baseview
+
+    def add_view(
+        self,
+        baseview,
+        name,
+        href="",
+        icon="",
+        label="",
+        category="",
+        category_icon="",
+        category_label="",
+        menu_cond=None,
+    ):
+        """
+        Add your views associated with menus using this method.
+
+        :param baseview:
+            A BaseView type class instantiated or not.
+            This method will instantiate the class for you if needed.
+        :param name:
+            The string name that identifies the menu.
+        :param href:
+            Override the generated href for the menu.
+            You can use an url string or an endpoint name
+            if non provided default_view from view will be set as href.
+        :param icon:
+            Font-Awesome icon name, optional.
+        :param label:
+            The label that will be displayed on the menu,
+            if absent param name will be used
+        :param category:
+            The menu category where the menu will be included,
+            if non provided the view will be accessible as a top menu.
+        :param category_icon:
+            Font-Awesome icon name for the category, optional.
+        :param category_label:
+            The label that will be displayed on the menu,
+            if absent param name will be used
+        :param menu_cond:
+            If a callable, :code:`menu_cond` will be invoked when
+            constructing the menu items. If it returns :code:`True`,
+            then this link will be a part of the menu. Otherwise, it
+            will not be included in the menu items. Defaults to
+            :code:`None`, meaning the item will always be present.
+
+        Examples::
+
+            appbuilder = AppBuilder(app, db)
+            # Register a view, rendering a top menu without icon.
+            appbuilder.add_view(MyModelView(), "My View")
+            # or not instantiated
+            appbuilder.add_view(MyModelView, "My View")
+            # Register a view, a submenu "Other View" from "Other" with a phone icon.
+            appbuilder.add_view(MyOtherModelView, "Other View", icon="fa-phone", category="Others")
+            # Register a view, with category icon and translation.
+            appbuilder.add_view(
+                YetOtherModelView,
+                "Other View",
+                icon="fa-phone",
+                label=_("Other View"),
+                category="Others",
+                category_icon="fa-envelop",
+                category_label=_("Other View"),
+            )
+            # Register a view whose menu item will be conditionally displayed
+            appbuilder.add_view(
+                YourFeatureView,
+                "Your Feature",
+                icon="fa-feature",
+                label=_("Your Feature"),
+                menu_cond=lambda: is_feature_enabled("your-feature"),
+            )
+            # Add a link
+            appbuilder.add_link("google", href="www.google.com", icon="fa-google-plus")
+        """
+        baseview = self._check_and_init(baseview)
+        log.info(LOGMSG_INF_FAB_ADD_VIEW, baseview.__class__.__name__, name)
+
+        if not self._view_exists(baseview):
+            baseview.appbuilder = self
+            self.baseviews.append(baseview)
+            self._process_inner_views()
+            if self.app:
+                self.register_blueprint(baseview)
+                self._add_permission(baseview)
+                self.add_limits(baseview)
+        self.add_link(
+            name=name,
+            href=href,
+            icon=icon,
+            label=label,
+            category=category,
+            category_icon=category_icon,
+            category_label=category_label,
+            baseview=baseview,
+            cond=menu_cond,
+        )
+        return baseview
+
+    def add_link(
+        self,
+        name,
+        href,
+        icon="",
+        label="",
+        category="",
+        category_icon="",
+        category_label="",
+        baseview=None,
+        cond=None,
+    ):
+        """
+        Add your own links to menu using this method.
+
+        :param name:
+            The string name that identifies the menu.
+        :param href:
+            Override the generated href for the menu.
+            You can use an url string or an endpoint name
+        :param icon:
+            Font-Awesome icon name, optional.
+        :param label:
+            The label that will be displayed on the menu,
+            if absent param name will be used
+        :param category:
+            The menu category where the menu will be included,
+            if non provided the view will be accessible as a top menu.
+        :param category_icon:
+            Font-Awesome icon name for the category, optional.
+        :param category_label:
+            The label that will be displayed on the menu,
+            if absent param name will be used
+        :param baseview:
+            A BaseView type class instantiated.
+        :param cond:
+            If a callable, :code:`cond` will be invoked when
+            constructing the menu items. If it returns :code:`True`,
+            then this link will be a part of the menu. Otherwise, it
+            will not be included in the menu items. Defaults to
+            :code:`None`, meaning the item will always be present.
+        """
+        self.menu.add_link(
+            name=name,
+            href=href,
+            icon=icon,
+            label=label,
+            category=category,
+            category_icon=category_icon,
+            category_label=category_label,
+            baseview=baseview,
+            cond=cond,
+        )
+        if self.app:
+            self._add_permissions_menu(name)
+            if category:
+                self._add_permissions_menu(category)
+
+    def add_separator(self, category, cond=None):
+        """
+        Add a separator to the menu, you will sequentially create the menu.
+
+        :param category:
+            The menu category where the separator will be included.
+        :param cond:
+            If a callable, :code:`cond` will be invoked when
+            constructing the menu items. If it returns :code:`True`,
+            then this separator will be a part of the menu. Otherwise,
+            it will not be included in the menu items. Defaults to
+            :code:`None`, meaning the separator will always be present.
+        """
+        self.menu.add_separator(category, cond=cond)
+
+    def add_view_no_menu(self, baseview, endpoint=None, static_folder=None):
+        """
+        Add your views without creating a menu.
+
+        :param baseview: A BaseView type class instantiated.
+        """
+        baseview = self._check_and_init(baseview)
+        log.info(LOGMSG_INF_FAB_ADD_VIEW, baseview.__class__.__name__, "")
+
+        if not self._view_exists(baseview):
+            baseview.appbuilder = self
+            self.baseviews.append(baseview)
+            self._process_inner_views()
+            if self.app:
+                self.register_blueprint(baseview, endpoint=endpoint, static_folder=static_folder)
+                self._add_permission(baseview)
+        else:
+            log.warning(LOGMSG_WAR_FAB_VIEW_EXISTS, baseview.__class__.__name__)
+        return baseview
+
+    @property
+    def get_url_for_index(self):
+        # TODO: Return the fast api application homepage

Review Comment:
   @pierrejeambrun @bbovenzi Is there any constant/method I can use which refer to the Airflow 3 homepage? For context, we are embedding a minimal version of Airflow 2 in fastapi application to be able to run Airflow 2 plugins from users. I'd like to redirect users to the Airflow 3 homepage whenever they click on the Airflow logo



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on code in PR #44464:
URL: https://github.com/apache/airflow/pull/44464#discussion_r1870096817


##########
providers/src/airflow/providers/fab/www/extensions/init_appbuilder.py:
##########
@@ -0,0 +1,580 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# mypy: disable-error-code=var-annotated
+from __future__ import annotations
+
+import logging
+from functools import reduce
+from typing import TYPE_CHECKING
+
+from flask import Blueprint, current_app, url_for
+from flask_appbuilder import __version__
+from flask_appbuilder.babel.manager import BabelManager
+from flask_appbuilder.const import (
+    LOGMSG_ERR_FAB_ADD_PERMISSION_MENU,
+    LOGMSG_ERR_FAB_ADD_PERMISSION_VIEW,
+    LOGMSG_ERR_FAB_ADDON_IMPORT,
+    LOGMSG_ERR_FAB_ADDON_PROCESS,
+    LOGMSG_INF_FAB_ADD_VIEW,
+    LOGMSG_INF_FAB_ADDON_ADDED,
+    LOGMSG_WAR_FAB_VIEW_EXISTS,
+)
+from flask_appbuilder.filters import TemplateFilters
+from flask_appbuilder.menu import Menu
+from flask_appbuilder.views import IndexView
+
+from airflow import settings
+from airflow.configuration import conf
+from airflow.www.extensions.init_auth_manager import init_auth_manager

Review Comment:
   I found a solution. I put a temporary fix in the method `get_auth_manager` in `airflow/api_fastapi/app.py`. I had to do that because for now we have 2 Flask application (the main one and the new one we are creating here)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on code in PR #44464:
URL: https://github.com/apache/airflow/pull/44464#discussion_r1869907129


##########
providers/src/airflow/providers/fab/www/static/js/toggle_theme.js:
##########
@@ -0,0 +1,46 @@
+/*!

Review Comment:
   Done



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "jedcunningham (via GitHub)" <gi...@apache.org>.
jedcunningham commented on code in PR #44464:
URL: https://github.com/apache/airflow/pull/44464#discussion_r1872170049


##########
airflow/api_fastapi/core_api/app.py:
##########
@@ -98,6 +100,41 @@ def init_plugins(app: FastAPI) -> None:
         app.mount(url_prefix, subapp)
 
 
+def init_flask_plugins(app: FastAPI) -> None:
+    """Integrate Flask plugins (plugins from Airflow 2)."""
+    from airflow import plugins_manager
+
+    plugins_manager.initialize_flask_plugins()
+
+    # If no Airflow 2.x plugin is in the environment, no need to go further
+    if (
+        not plugins_manager.flask_blueprints
+        and not plugins_manager.flask_appbuilder_views
+        and not plugins_manager.flask_appbuilder_menu_links
+    ):
+        return
+
+    from fastapi.middleware.wsgi import WSGIMiddleware
+
+    try:
+        from airflow.providers.fab.www.app import create_app
+    except ImportError:
+        raise AirflowException(
+            "Some Airflow 2 plugins have been detected in your environment. "
+            "To run them with Airflow 3, you must install fab provider in your Airflow environment."

Review Comment:
   ```suggestion
               "To run them with Airflow 3, you must install the FAB provider in your Airflow environment."
   ```
   
   nit



##########
providers/src/airflow/providers/fab/www/templates/swagger-ui/index.j2:
##########
@@ -0,0 +1,87 @@
+{#

Review Comment:
   hmm, do we need the swagger ui stuff still? That's only the api reference, right?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on PR #44464:
URL: https://github.com/apache/airflow/pull/44464#issuecomment-2521377452

   Tests are passing, let me know if there are more comments/feedbacks


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on PR #44464:
URL: https://github.com/apache/airflow/pull/44464#issuecomment-2515494838

   This is a preview of the page when accessing a plugin.
   
   <img width="1720" alt="Screenshot 2024-12-03 at 3 31 25 PM" src="https://github.com/user-attachments/assets/8bc2be87-1f7d-49bb-af60-944fa1f23fe6">
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "jedcunningham (via GitHub)" <gi...@apache.org>.
jedcunningham commented on PR #44464:
URL: https://github.com/apache/airflow/pull/44464#issuecomment-2511999535

   I'd kinda prefer we don't mount the "whole" AF2 app as-is in AF3, even if we do view it as temporary.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "vincbeck (via GitHub)" <gi...@apache.org>.
vincbeck commented on PR #44464:
URL: https://github.com/apache/airflow/pull/44464#issuecomment-2512158245

   > I'd kinda prefer we don't mount the "whole" AF2 app as-is in AF3, even if we do view it as temporary.
   
   Alright, I'll do it in this PR then


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] AIP-79 Support Airflow 2.x plugins in fast api [airflow]

Posted by "jedcunningham (via GitHub)" <gi...@apache.org>.
jedcunningham commented on PR #44464:
URL: https://github.com/apache/airflow/pull/44464#issuecomment-2509166409

   Instead of waiting for #2 to happen, let's just copy/trim whatever minimal FAB stuff we need into the FAB provider now - just leave it completely separate from the old UI. Then we aren't blocked at all.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org