Skip to content

Commit f22f756

Browse files
Adjustment external views (#51889)
* Adjustment external views * Fix lint * Fix CI * Improvements * Small update * Update following code review
1 parent ecab0fa commit f22f756

16 files changed

Lines changed: 421 additions & 259 deletions

File tree

airflow-core/docs/administration-and-deployment/plugins.rst

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ looks like:
109109
# A list of dictionaries containing FastAPI middleware factory objects and some metadata. See the example below.
110110
fastapi_root_middlewares = []
111111
# A list of dictionaries containing iframe views and some metadata. See the example below.
112-
iframe_views = []
112+
external_views = []
113113
114114
# A callback to perform actions when Airflow starts and the plugin is loaded.
115115
# NOTE: Ensure your plugin has *args, and **kwargs in the method definition
@@ -195,18 +195,24 @@ definitions in Airflow.
195195
}
196196
197197
# Creating a iframe view that will be rendered in the Airflow UI.
198-
iframe_view_with_metadata = {
198+
external_view_with_metadata = {
199199
"name": "Name of the Iframe View as displayed in the UI",
200-
# Source URL of the iframe. This URL can be templated using context variables, depending on the location where the iframe is rendered
201-
# the context variables available will be different, i.e a subset of (DAG_ID, RUN_ID, TASK_ID, MAP_INDEX)
202-
"src": "https://example.com/{DAG_ID}/{RUN_ID}/{TASK_ID}",
200+
# Source URL of the external view. This URL can be templated using context variables, depending on the location where the external view is rendered
201+
# the context variables available will be different, i.e a subset of (DAG_ID, RUN_ID, TASK_ID, MAP_INDEX).
202+
"href": "https://example.com/{DAG_ID}/{RUN_ID}/{TASK_ID}",
203203
# Destination of the iframe view. This is used to determine where the iframe will be loaded in the UI.
204-
# Supported locations are Literal["nav", "dag", "dag_run", "task", "task_instance"]
204+
# Supported locations are Literal["nav", "dag", "dag_run", "task", "task_instance"], default to "nav".
205205
"destination": "dag_run",
206206
# Optional icon, url to an svg file.
207207
"icon": "https://example.com/icon.svg",
208-
# Optional parameters, relative URL location when opening the iframe
209-
"url_route": "/my_iframe_view",
208+
# Optional dark icon for the dark theme, url to an svg file. If not provided, "icon" will be used for both light and dark themes.
209+
"icon_dark_mode": "https://example.com/dark_icon.svg",
210+
# Optional parameters, relative URL location for the iframe rendering. If not provided, external view will be rendeded as an external link. Should
211+
# not contain a leading slash.
212+
"url_route": "my_iframe_view",
213+
# Optional category, only relevant for destination "nav". This is used to group the external links in the navigation bar. We will match the existing
214+
# menus of ["browse", "docs", "admin", "user"] and if there's no match then create a new menu.
215+
"category": "browse",
210216
}
211217
212218
@@ -216,7 +222,7 @@ definitions in Airflow.
216222
macros = [plugin_macro]
217223
fastapi_apps = [app_with_metadata]
218224
fastapi_root_middlewares = [middleware_with_metadata]
219-
iframe_views = [iframe_view_with_metadata]
225+
external_views = [external_view_with_metadata]
220226
221227
.. seealso:: :doc:`/howto/define-extra-link`
222228

airflow-core/docs/howto/custom-view-plugin.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ core UI using the Plugin manager.
2424

2525
Plugins integrate with the Airflow core RestAPI. In this plugin,
2626
three object references are derived from the base class ``airflow.plugins_manager.AirflowPlugin``.
27-
They are fastapi_apps, fastapi_root_middlewares and iframe_views.
27+
They are fastapi_apps, fastapi_root_middlewares and external_views.
2828

2929
Using fastapi_apps in Airflow plugin, the core RestAPI can be extended
3030
to support extra endpoints to serve custom static file or any other json/application responses.
@@ -37,12 +37,12 @@ functionality to the entire FastAPI application, including core endpoints.
3737
In this object reference, the list of dictionaries with Middleware factories object,
3838
initialization parameters and some metadata information like the name are passed on.
3939

40-
Using iframe_views in Airflow plugin, allows to register custom views that are rendered in iframes in
41-
the Airflow UI. This is useful for integrating external applications or custom dashboards into the Airflow UI.
40+
Using external_views in Airflow plugin, allows to register custom views that are rendered in iframes or external link
41+
in the Airflow UI. This is useful for integrating external applications or custom dashboards into the Airflow UI.
4242
In this object reference, the list of dictionaries with the view name, iframe src (templatable), destination and
4343
optional parameters like the icon and url_route are passed on.
4444

45-
Information and code samples to register ``fastapi_apps``, ``fastapi_root_middlewares`` and ``iframe_views`` are
45+
Information and code samples to register ``fastapi_apps``, ``fastapi_root_middlewares`` and ``external_views`` are
4646
available in :doc:`plugin </administration-and-deployment/plugins>`.
4747

4848
Support for Airflow 2 plugins

airflow-core/src/airflow/api_fastapi/core_api/datamodels/plugins.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from typing import Annotated, Any, Literal
2121

22-
from pydantic import BeforeValidator, ConfigDict, field_validator
22+
from pydantic import BeforeValidator, ConfigDict, Field, field_validator, model_validator
2323

2424
from airflow.api_fastapi.core_api.base import BaseModel
2525
from airflow.plugins_manager import AirflowPluginSource
@@ -65,20 +65,22 @@ class AppBuilderMenuItemResponse(BaseModel):
6565
model_config = ConfigDict(extra="allow")
6666

6767
name: str
68-
href: str | None = None
68+
href: str
6969
category: str | None = None
7070

7171

72-
class IFrameViewsResponse(BaseModel):
72+
class ExternalViewResponse(BaseModel):
7373
"""Serializer for IFrame Plugin responses."""
7474

7575
model_config = ConfigDict(extra="allow")
7676

7777
name: str
78-
src: str
78+
href: str
7979
icon: str | None = None
80+
icon_dark_mode: str | None = None
8081
url_route: str | None = None
81-
destination: Literal["nav", "dag", "dag_run", "task", "task_instance"] | None = None
82+
category: str | None = None
83+
destination: Literal["nav", "dag", "dag_run", "task", "task_instance"] = "nav"
8284

8385

8486
class PluginResponse(BaseModel):
@@ -89,9 +91,13 @@ class PluginResponse(BaseModel):
8991
flask_blueprints: list[str]
9092
fastapi_apps: list[FastAPIAppResponse]
9193
fastapi_root_middlewares: list[FastAPIRootMiddlewareResponse]
92-
iframe_views: list[IFrameViewsResponse]
94+
external_views: list[ExternalViewResponse] = Field(
95+
description="Aggregate all external views. Both 'external_views' and 'appbuilder_menu_items' are included here."
96+
)
9397
appbuilder_views: list[AppBuilderViewResponse]
94-
appbuilder_menu_items: list[AppBuilderMenuItemResponse]
98+
appbuilder_menu_items: list[AppBuilderMenuItemResponse] = Field(
99+
deprecated="Kept for backward compatibility, use `external_views` instead.",
100+
)
95101
global_operator_extra_links: list[str]
96102
operator_extra_links: list[str]
97103
source: Annotated[str, BeforeValidator(coerce_to_string)]
@@ -105,6 +111,12 @@ def convert_source(cls, data: Any) -> Any:
105111
return str(data)
106112
return data
107113

114+
@model_validator(mode="before")
115+
@classmethod
116+
def convert_external_views(cls, data: Any) -> Any:
117+
data["external_views"] = [*data["external_views"], *data.get("appbuilder_menu_items", [])]
118+
return data
119+
108120

109121
class PluginCollectionResponse(BaseModel):
110122
"""Plugin Collection serializer."""

airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7029,9 +7029,7 @@ components:
70297029
type: string
70307030
title: Name
70317031
href:
7032-
anyOf:
7033-
- type: string
7034-
- type: 'null'
7032+
type: string
70357033
title: Href
70367034
category:
70377035
anyOf:
@@ -7042,6 +7040,7 @@ components:
70427040
type: object
70437041
required:
70447042
- name
7043+
- href
70457044
title: AppBuilderMenuItemResponse
70467045
description: Serializer for AppBuilder Menu Item responses.
70477046
AppBuilderViewResponse:
@@ -9293,6 +9292,51 @@ components:
92939292
- url
92949293
title: ExternalLogUrlResponse
92959294
description: Response for the external log URL endpoint.
9295+
ExternalViewResponse:
9296+
properties:
9297+
name:
9298+
type: string
9299+
title: Name
9300+
href:
9301+
type: string
9302+
title: Href
9303+
icon:
9304+
anyOf:
9305+
- type: string
9306+
- type: 'null'
9307+
title: Icon
9308+
icon_dark_mode:
9309+
anyOf:
9310+
- type: string
9311+
- type: 'null'
9312+
title: Icon Dark Mode
9313+
url_route:
9314+
anyOf:
9315+
- type: string
9316+
- type: 'null'
9317+
title: Url Route
9318+
category:
9319+
anyOf:
9320+
- type: string
9321+
- type: 'null'
9322+
title: Category
9323+
destination:
9324+
type: string
9325+
enum:
9326+
- nav
9327+
- dag
9328+
- dag_run
9329+
- task
9330+
- task_instance
9331+
title: Destination
9332+
default: nav
9333+
additionalProperties: true
9334+
type: object
9335+
required:
9336+
- name
9337+
- href
9338+
title: ExternalViewResponse
9339+
description: Serializer for IFrame Plugin responses.
92969340
ExtraLinkCollectionResponse:
92979341
properties:
92989342
extra_links:
@@ -9386,42 +9430,6 @@ components:
93869430
- triggerer
93879431
title: HealthInfoResponse
93889432
description: Health serializer for responses.
9389-
IFrameViewsResponse:
9390-
properties:
9391-
name:
9392-
type: string
9393-
title: Name
9394-
src:
9395-
type: string
9396-
title: Src
9397-
icon:
9398-
anyOf:
9399-
- type: string
9400-
- type: 'null'
9401-
title: Icon
9402-
url_route:
9403-
anyOf:
9404-
- type: string
9405-
- type: 'null'
9406-
title: Url Route
9407-
destination:
9408-
anyOf:
9409-
- type: string
9410-
enum:
9411-
- nav
9412-
- dag
9413-
- dag_run
9414-
- task
9415-
- task_instance
9416-
- type: 'null'
9417-
title: Destination
9418-
additionalProperties: true
9419-
type: object
9420-
required:
9421-
- name
9422-
- src
9423-
title: IFrameViewsResponse
9424-
description: Serializer for IFrame Plugin responses.
94259433
ImportErrorCollectionResponse:
94269434
properties:
94279435
import_errors:
@@ -9676,11 +9684,13 @@ components:
96769684
$ref: '#/components/schemas/FastAPIRootMiddlewareResponse'
96779685
type: array
96789686
title: Fastapi Root Middlewares
9679-
iframe_views:
9687+
external_views:
96809688
items:
9681-
$ref: '#/components/schemas/IFrameViewsResponse'
9689+
$ref: '#/components/schemas/ExternalViewResponse'
96829690
type: array
9683-
title: Iframe Views
9691+
title: External Views
9692+
description: Aggregate all external views. Both 'external_views' and 'appbuilder_menu_items'
9693+
are included here.
96849694
appbuilder_views:
96859695
items:
96869696
$ref: '#/components/schemas/AppBuilderViewResponse'
@@ -9691,6 +9701,7 @@ components:
96919701
$ref: '#/components/schemas/AppBuilderMenuItemResponse'
96929702
type: array
96939703
title: Appbuilder Menu Items
9704+
deprecated: true
96949705
global_operator_extra_links:
96959706
items:
96969707
type: string
@@ -9721,7 +9732,7 @@ components:
97219732
- flask_blueprints
97229733
- fastapi_apps
97239734
- fastapi_root_middlewares
9724-
- iframe_views
9735+
- external_views
97259736
- appbuilder_views
97269737
- appbuilder_menu_items
97279738
- global_operator_extra_links

airflow-core/src/airflow/plugins_manager.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
flask_blueprints: list[Any] | None = None
7070
fastapi_apps: list[Any] | None = None
7171
fastapi_root_middlewares: list[Any] | None = None
72-
iframe_views: list[Any] | None = None
72+
external_views: list[Any] | None = None
7373
menu_links: list[Any] | None = None
7474
flask_appbuilder_views: list[Any] | None = None
7575
flask_appbuilder_menu_links: list[Any] | None = None
@@ -91,7 +91,7 @@
9191
"flask_blueprints",
9292
"fastapi_apps",
9393
"fastapi_root_middlewares",
94-
"iframe_views",
94+
"external_views",
9595
"menu_links",
9696
"appbuilder_views",
9797
"appbuilder_menu_items",
@@ -156,7 +156,7 @@ class AirflowPlugin:
156156
flask_blueprints: list[Any] = []
157157
fastapi_apps: list[Any] = []
158158
fastapi_root_middlewares: list[Any] = []
159-
iframe_views: list[Any] = []
159+
external_views: list[Any] = []
160160
menu_links: list[Any] = []
161161
appbuilder_views: list[Any] = []
162162
appbuilder_menu_items: list[Any] = []
@@ -371,9 +371,9 @@ def ensure_plugins_loaded():
371371
def initialize_ui_plugins():
372372
"""Collect extension points for the UI."""
373373
global plugins
374-
global iframe_views
374+
global external_views
375375

376-
if iframe_views is not None:
376+
if external_views is not None:
377377
return
378378

379379
ensure_plugins_loaded()
@@ -383,10 +383,10 @@ def initialize_ui_plugins():
383383

384384
log.debug("Initialize UI plugin")
385385

386-
iframe_views = []
386+
external_views = []
387387

388388
for plugin in plugins:
389-
iframe_views.extend(plugin.iframe_views)
389+
external_views.extend(plugin.external_views)
390390

391391

392392
def initialize_flask_plugins():

0 commit comments

Comments
 (0)