Skip to content

Commit ece7548

Browse files
Add React Apps to plugin
1 parent 42cba9b commit ece7548

File tree

12 files changed

+300
-64
lines changed

12 files changed

+300
-64
lines changed

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

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,10 @@ looks like:
108108
fastapi_apps = []
109109
# A list of dictionaries containing FastAPI middleware factory objects and some metadata. See the example below.
110110
fastapi_root_middlewares = []
111-
# A list of dictionaries containing iframe views and some metadata. See the example below.
111+
# A list of dictionaries containing external views and some metadata. See the example below.
112112
external_views = []
113+
# A list of dictionaries containing react apps and some metadata. See the example below.
114+
react_apps = []
113115
114116
# A callback to perform actions when Airflow starts and the plugin is loaded.
115117
# NOTE: Ensure your plugin has *args, and **kwargs in the method definition
@@ -194,27 +196,51 @@ definitions in Airflow.
194196
"name": "Name of the Middleware",
195197
}
196198
197-
# Creating a iframe view that will be rendered in the Airflow UI.
199+
# Creating an external view that will be rendered in the Airflow UI.
198200
external_view_with_metadata = {
199-
"name": "Name of the Iframe View as displayed in the UI",
201+
# Name of the external view, this will be displayed in the UI.
202+
"name": "Name of the External View",
200203
# Source URL of the external view. This URL can be templated using context variables, depending on the location where the external view is rendered
201204
# 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}",
203-
# Destination of the iframe view. This is used to determine where the iframe will be loaded in the UI.
205+
"href": "https://example.com/{DAG_ID}/{RUN_ID}/{TASK_ID}/{MAP_INDEX}",
206+
# Destination of the external view. This is used to determine where the view will be loaded in the UI.
204207
# Supported locations are Literal["nav", "dag", "dag_run", "task", "task_instance"], default to "nav".
208+
# It can also be put inside of an existing page, the supported views are ["dashboard", "dag_overview", "task_overview"]
205209
"destination": "dag_run",
206210
# Optional icon, url to an svg file.
207211
"icon": "https://example.com/icon.svg",
208212
# 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.
209213
"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",
214+
# Optional parameters, relative URL location for the External View rendering. If not provided, external view will be rendeded as an external link. If provided
215+
# will be rendered inside an Iframe in the UI. Should not contain a leading slash.
216+
"url_route": "my_external_view",
213217
# Optional category, only relevant for destination "nav". This is used to group the external links in the navigation bar. We will match the existing
214218
# menus of ["browse", "docs", "admin", "user"] and if there's no match then create a new menu.
215219
"category": "browse",
216220
}
217221
222+
react_app_with_metadata = {
223+
# Name of the React app, this will be displayed in the UI.
224+
"name": "Name of the React App",
225+
# Bundle URL of the React app. This is the URL where the React app is served from. It can be a static file or a CDN.
226+
# This URL can be templated using context variables, depending on the location where the external view is rendered
227+
# the context variables available will be different, i.e a subset of (DAG_ID, RUN_ID, TASK_ID, MAP_INDEX).
228+
"bundle_url": "https://example.com/static/js/my_react_app.js",
229+
# Destination of the react app. This is used to determine where the app will be loaded in the UI.
230+
# Supported locations are Literal["nav", "dag", "dag_run", "task", "task_instance"], default to "nav".
231+
# It can also be put inside of an existing page, the supported views are ["dashboard", "dag_overview", "task_overview"]
232+
"destination": "dag_run",
233+
# Optional icon, url to an svg file.
234+
"icon": "https://example.com/icon.svg",
235+
# 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.
236+
"icon_dark_mode": "https://example.com/dark_icon.svg",
237+
# URL route for the React app, relative to the Airflow UI base URL. Should not contain a leading slash.
238+
"url_route": "my_react_app",
239+
# Optional category, only relevant for destination "nav". This is used to group the react apps in the navigation bar. We will match the existing
240+
# menus of ["browse", "docs", "admin", "user"] and if there's no match then create a new menu.
241+
"category": "browse",
242+
}
243+
218244
219245
# Defining the plugin class
220246
class AirflowTestPlugin(AirflowPlugin):
@@ -223,6 +249,7 @@ definitions in Airflow.
223249
fastapi_apps = [app_with_metadata]
224250
fastapi_root_middlewares = [middleware_with_metadata]
225251
external_views = [external_view_with_metadata]
252+
react_apps = [react_app_with_metadata]
226253
227254
.. seealso:: :doc:`/howto/define-extra-link`
228255

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

Lines changed: 9 additions & 3 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 external_views.
27+
They are fastapi_apps, fastapi_root_middlewares, external_views and react_apps.
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.
@@ -39,10 +39,16 @@ initialization parameters and some metadata information like the name are passed
3939

4040
Using external_views in Airflow plugin, allows to register custom views that are rendered in iframes or external link
4141
in the Airflow UI. This is useful for integrating external applications or custom dashboards into the Airflow UI.
42-
In this object reference, the list of dictionaries with the view name, iframe src (templatable), destination and
42+
In this object reference, the list of dictionaries with the view name, href (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 ``external_views`` are
45+
Using react_apps in Airflow plugin, allows to register custom React applications that can be rendered
46+
in the Airflow UI. This is useful for integrating custom React components or applications into the Airflow UI.
47+
In this object reference, the list of dictionaries with the app name, bundle_url (where to load the js assets, templatable), destination and
48+
optional parameters like the icon and url_route are passed on.
49+
50+
51+
Information and code samples to register ``fastapi_apps``, ``fastapi_root_middlewares``, ``external_views`` and ``react_apps`` are
4652
available in :doc:`plugin </administration-and-deployment/plugins>`.
4753

4854
Support for Airflow 2 plugins

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,35 @@ class AppBuilderMenuItemResponse(BaseModel):
6969
category: str | None = None
7070

7171

72-
class ExternalViewResponse(BaseModel):
73-
"""Serializer for IFrame Plugin responses."""
72+
class BaseUIResponse(BaseModel):
73+
"""Base serializer for UI Plugin responses."""
7474

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

7777
name: str
78-
href: str
7978
icon: str | None = None
8079
icon_dark_mode: str | None = None
8180
url_route: str | None = None
8281
category: str | None = None
8382
destination: Literal["nav", "dag", "dag_run", "task", "task_instance"] = "nav"
8483

8584

85+
class ExternalViewResponse(BaseUIResponse):
86+
"""Serializer for External View Plugin responses."""
87+
88+
model_config = ConfigDict(extra="allow")
89+
90+
href: str
91+
92+
93+
class ReactAppResponse(BaseUIResponse):
94+
"""Serializer for React App Plugin responses."""
95+
96+
model_config = ConfigDict(extra="allow")
97+
98+
bundle_url: str
99+
100+
86101
class PluginResponse(BaseModel):
87102
"""Plugin serializer."""
88103

@@ -94,6 +109,7 @@ class PluginResponse(BaseModel):
94109
external_views: list[ExternalViewResponse] = Field(
95110
description="Aggregate all external views. Both 'external_views' and 'appbuilder_menu_items' are included here."
96111
)
112+
react_apps: list[ReactAppResponse]
97113
appbuilder_views: list[AppBuilderViewResponse]
98114
appbuilder_menu_items: list[AppBuilderMenuItemResponse] = Field(
99115
deprecated="Kept for backward compatibility, use `external_views` instead.",

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

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9297,9 +9297,6 @@ components:
92979297
name:
92989298
type: string
92999299
title: Name
9300-
href:
9301-
type: string
9302-
title: Href
93039300
icon:
93049301
anyOf:
93059302
- type: string
@@ -9330,13 +9327,16 @@ components:
93309327
- task_instance
93319328
title: Destination
93329329
default: nav
9330+
href:
9331+
type: string
9332+
title: Href
93339333
additionalProperties: true
93349334
type: object
93359335
required:
93369336
- name
93379337
- href
93389338
title: ExternalViewResponse
9339-
description: Serializer for IFrame Plugin responses.
9339+
description: Serializer for External View Plugin responses.
93409340
ExtraLinkCollectionResponse:
93419341
properties:
93429342
extra_links:
@@ -9691,6 +9691,11 @@ components:
96919691
title: External Views
96929692
description: Aggregate all external views. Both 'external_views' and 'appbuilder_menu_items'
96939693
are included here.
9694+
react_apps:
9695+
items:
9696+
$ref: '#/components/schemas/ReactAppResponse'
9697+
type: array
9698+
title: React Apps
96949699
appbuilder_views:
96959700
items:
96969701
$ref: '#/components/schemas/AppBuilderViewResponse'
@@ -9733,6 +9738,7 @@ components:
97339738
- fastapi_apps
97349739
- fastapi_root_middlewares
97359740
- external_views
9741+
- react_apps
97369742
- appbuilder_views
97379743
- appbuilder_menu_items
97389744
- global_operator_extra_links
@@ -9930,6 +9936,51 @@ components:
99309936
- dag_display_name
99319937
title: QueuedEventResponse
99329938
description: Queued Event serializer for responses..
9939+
ReactAppResponse:
9940+
properties:
9941+
name:
9942+
type: string
9943+
title: Name
9944+
icon:
9945+
anyOf:
9946+
- type: string
9947+
- type: 'null'
9948+
title: Icon
9949+
icon_dark_mode:
9950+
anyOf:
9951+
- type: string
9952+
- type: 'null'
9953+
title: Icon Dark Mode
9954+
url_route:
9955+
anyOf:
9956+
- type: string
9957+
- type: 'null'
9958+
title: Url Route
9959+
category:
9960+
anyOf:
9961+
- type: string
9962+
- type: 'null'
9963+
title: Category
9964+
destination:
9965+
type: string
9966+
enum:
9967+
- nav
9968+
- dag
9969+
- dag_run
9970+
- task
9971+
- task_instance
9972+
title: Destination
9973+
default: nav
9974+
bundle_url:
9975+
type: string
9976+
title: Bundle Url
9977+
additionalProperties: true
9978+
type: object
9979+
required:
9980+
- name
9981+
- bundle_url
9982+
title: ReactAppResponse
9983+
description: Serializer for React App Plugin responses.
99339984
ReprocessBehavior:
99349985
type: string
99359986
enum:

airflow-core/src/airflow/plugins_manager.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
fastapi_apps: list[Any] | None = None
7171
fastapi_root_middlewares: list[Any] | None = None
7272
external_views: list[Any] | None = None
73+
react_apps: list[Any] | None = None
7374
menu_links: list[Any] | None = None
7475
flask_appbuilder_views: list[Any] | None = None
7576
flask_appbuilder_menu_links: list[Any] | None = None
@@ -92,6 +93,7 @@
9293
"fastapi_apps",
9394
"fastapi_root_middlewares",
9495
"external_views",
96+
"react_apps",
9597
"menu_links",
9698
"appbuilder_views",
9799
"appbuilder_menu_items",
@@ -157,6 +159,7 @@ class AirflowPlugin:
157159
fastapi_apps: list[Any] = []
158160
fastapi_root_middlewares: list[Any] = []
159161
external_views: list[Any] = []
162+
react_apps: list[Any] = []
160163
menu_links: list[Any] = []
161164
appbuilder_views: list[Any] = []
162165
appbuilder_menu_items: list[Any] = []
@@ -372,8 +375,9 @@ def initialize_ui_plugins():
372375
"""Collect extension points for the UI."""
373376
global plugins
374377
global external_views
378+
global react_apps
375379

376-
if external_views is not None:
380+
if external_views is not None and react_apps is not None:
377381
return
378382

379383
ensure_plugins_loaded()
@@ -384,9 +388,11 @@ def initialize_ui_plugins():
384388
log.debug("Initialize UI plugin")
385389

386390
external_views = []
391+
react_apps = []
387392

388393
for plugin in plugins:
389394
external_views.extend(plugin.external_views)
395+
react_apps.extend(plugin.react_apps)
390396

391397

392398
def initialize_flask_plugins():

0 commit comments

Comments
 (0)