From 9e7dd24eb4d624c68bc1ef81e12d12a550132368 Mon Sep 17 00:00:00 2001 From: pierrejeambrun Date: Wed, 25 Jun 2025 17:45:47 +0200 Subject: [PATCH] Add React Apps to plugin --- .../administration-and-deployment/plugins.rst | 42 +++++++-- .../docs/howto/custom-view-plugin.rst | 12 ++- .../core_api/datamodels/plugins.py | 22 ++++- .../openapi/v2-rest-api-generated.yaml | 59 +++++++++++- airflow-core/src/airflow/plugins_manager.py | 8 +- .../ui/openapi-gen/requests/schemas.gen.ts | 87 ++++++++++++++++-- .../ui/openapi-gen/requests/types.gen.ts | 19 +++- .../unit/cli/commands/test_plugins_command.py | 10 ++ .../tests/unit/plugins/test_plugin.py | 10 ++ .../airflowctl/api/datamodels/generated.py | 92 +++++++++++-------- .../tests_common/test_utils/mock_plugins.py | 1 + docs/spelling_wordlist.txt | 1 + 12 files changed, 299 insertions(+), 64 deletions(-) diff --git a/airflow-core/docs/administration-and-deployment/plugins.rst b/airflow-core/docs/administration-and-deployment/plugins.rst index 2daa6036ddb11..c0a1b5129db59 100644 --- a/airflow-core/docs/administration-and-deployment/plugins.rst +++ b/airflow-core/docs/administration-and-deployment/plugins.rst @@ -108,8 +108,10 @@ looks like: fastapi_apps = [] # A list of dictionaries containing FastAPI middleware factory objects and some metadata. See the example below. fastapi_root_middlewares = [] - # A list of dictionaries containing iframe views and some metadata. See the example below. + # A list of dictionaries containing external views and some metadata. See the example below. external_views = [] + # A list of dictionaries containing react apps and some metadata. See the example below. + react_apps = [] # A callback to perform actions when Airflow starts and the plugin is loaded. # NOTE: Ensure your plugin has *args, and **kwargs in the method definition @@ -194,27 +196,50 @@ definitions in Airflow. "name": "Name of the Middleware", } - # Creating a iframe view that will be rendered in the Airflow UI. + # Creating an external view that will be rendered in the Airflow UI. external_view_with_metadata = { - "name": "Name of the Iframe View as displayed in the UI", + # Name of the external view, this will be displayed in the UI. + "name": "Name of the External View", # Source URL of the external view. This URL can be templated using context variables, depending on the location where the external view is rendered # the context variables available will be different, i.e a subset of (DAG_ID, RUN_ID, TASK_ID, MAP_INDEX). - "href": "https://example.com/{DAG_ID}/{RUN_ID}/{TASK_ID}", - # Destination of the iframe view. This is used to determine where the iframe will be loaded in the UI. + "href": "https://example.com/{DAG_ID}/{RUN_ID}/{TASK_ID}/{MAP_INDEX}", + # Destination of the external view. This is used to determine where the view will be loaded in the UI. # Supported locations are Literal["nav", "dag", "dag_run", "task", "task_instance"], default to "nav". "destination": "dag_run", # Optional icon, url to an svg file. "icon": "https://example.com/icon.svg", # 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. "icon_dark_mode": "https://example.com/dark_icon.svg", - # Optional parameters, relative URL location for the iframe rendering. If not provided, external view will be rendeded as an external link. Should - # not contain a leading slash. - "url_route": "my_iframe_view", + # Optional parameters, relative URL location for the External View rendering. If not provided, external view will be rendeded as an external link. If provided + # will be rendered inside an Iframe in the UI. Should not contain a leading slash. + "url_route": "my_external_view", # Optional category, only relevant for destination "nav". This is used to group the external links in the navigation bar. We will match the existing # menus of ["browse", "docs", "admin", "user"] and if there's no match then create a new menu. "category": "browse", } + react_app_with_metadata = { + # Name of the React app, this will be displayed in the UI. + "name": "Name of the React App", + # 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. + # This URL can be templated using context variables, depending on the location where the external view is rendered + # the context variables available will be different, i.e a subset of (DAG_ID, RUN_ID, TASK_ID, MAP_INDEX). + "bundle_url": "https://example.com/static/js/my_react_app.js", + # Destination of the react app. This is used to determine where the app will be loaded in the UI. + # Supported locations are Literal["nav", "dag", "dag_run", "task", "task_instance"], default to "nav". + # It can also be put inside of an existing page, the supported views are ["dashboard", "dag_overview", "task_overview"] + "destination": "dag_run", + # Optional icon, url to an svg file. + "icon": "https://example.com/icon.svg", + # 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. + "icon_dark_mode": "https://example.com/dark_icon.svg", + # URL route for the React app, relative to the Airflow UI base URL. Should not contain a leading slash. + "url_route": "my_react_app", + # Optional category, only relevant for destination "nav". This is used to group the react apps in the navigation bar. We will match the existing + # menus of ["browse", "docs", "admin", "user"] and if there's no match then create a new menu. + "category": "browse", + } + # Defining the plugin class class AirflowTestPlugin(AirflowPlugin): @@ -223,6 +248,7 @@ definitions in Airflow. fastapi_apps = [app_with_metadata] fastapi_root_middlewares = [middleware_with_metadata] external_views = [external_view_with_metadata] + react_apps = [react_app_with_metadata] .. seealso:: :doc:`/howto/define-extra-link` diff --git a/airflow-core/docs/howto/custom-view-plugin.rst b/airflow-core/docs/howto/custom-view-plugin.rst index 74a2891f47615..38111354b9f5b 100644 --- a/airflow-core/docs/howto/custom-view-plugin.rst +++ b/airflow-core/docs/howto/custom-view-plugin.rst @@ -24,7 +24,7 @@ core UI using the Plugin manager. Plugins integrate with the Airflow core RestAPI. In this plugin, three object references are derived from the base class ``airflow.plugins_manager.AirflowPlugin``. -They are fastapi_apps, fastapi_root_middlewares and external_views. +They are fastapi_apps, fastapi_root_middlewares, external_views and react_apps. Using fastapi_apps in Airflow plugin, the core RestAPI can be extended 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 Using external_views in Airflow plugin, allows to register custom views that are rendered in iframes or external link in the Airflow UI. This is useful for integrating external applications or custom dashboards into the Airflow UI. -In this object reference, the list of dictionaries with the view name, iframe src (templatable), destination and +In this object reference, the list of dictionaries with the view name, href (templatable), destination and optional parameters like the icon and url_route are passed on. -Information and code samples to register ``fastapi_apps``, ``fastapi_root_middlewares`` and ``external_views`` are +Using react_apps in Airflow plugin, allows to register custom React applications that can be rendered +in the Airflow UI. This is useful for integrating custom React components or applications into the Airflow UI. +In this object reference, the list of dictionaries with the app name, bundle_url (where to load the js assets, templatable), destination and +optional parameters like the icon and url_route are passed on. + + +Information and code samples to register ``fastapi_apps``, ``fastapi_root_middlewares``, ``external_views`` and ``react_apps`` are available in :doc:`plugin `. Support for Airflow 2 plugins diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/plugins.py b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/plugins.py index daf712a8eda0e..61d4ec8a0842b 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/plugins.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/plugins.py @@ -69,13 +69,12 @@ class AppBuilderMenuItemResponse(BaseModel): category: str | None = None -class ExternalViewResponse(BaseModel): - """Serializer for IFrame Plugin responses.""" +class BaseUIResponse(BaseModel): + """Base serializer for UI Plugin responses.""" model_config = ConfigDict(extra="allow") name: str - href: str icon: str | None = None icon_dark_mode: str | None = None url_route: str | None = None @@ -83,6 +82,22 @@ class ExternalViewResponse(BaseModel): destination: Literal["nav", "dag", "dag_run", "task", "task_instance"] = "nav" +class ExternalViewResponse(BaseUIResponse): + """Serializer for External View Plugin responses.""" + + model_config = ConfigDict(extra="allow") + + href: str + + +class ReactAppResponse(BaseUIResponse): + """Serializer for React App Plugin responses.""" + + model_config = ConfigDict(extra="allow") + + bundle_url: str + + class PluginResponse(BaseModel): """Plugin serializer.""" @@ -94,6 +109,7 @@ class PluginResponse(BaseModel): external_views: list[ExternalViewResponse] = Field( description="Aggregate all external views. Both 'external_views' and 'appbuilder_menu_items' are included here." ) + react_apps: list[ReactAppResponse] appbuilder_views: list[AppBuilderViewResponse] appbuilder_menu_items: list[AppBuilderMenuItemResponse] = Field( deprecated="Kept for backward compatibility, use `external_views` instead.", diff --git a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml index 478316b43369a..a3fd6d5cbd805 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml +++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml @@ -9297,9 +9297,6 @@ components: name: type: string title: Name - href: - type: string - title: Href icon: anyOf: - type: string @@ -9330,13 +9327,16 @@ components: - task_instance title: Destination default: nav + href: + type: string + title: Href additionalProperties: true type: object required: - name - href title: ExternalViewResponse - description: Serializer for IFrame Plugin responses. + description: Serializer for External View Plugin responses. ExtraLinkCollectionResponse: properties: extra_links: @@ -9691,6 +9691,11 @@ components: title: External Views description: Aggregate all external views. Both 'external_views' and 'appbuilder_menu_items' are included here. + react_apps: + items: + $ref: '#/components/schemas/ReactAppResponse' + type: array + title: React Apps appbuilder_views: items: $ref: '#/components/schemas/AppBuilderViewResponse' @@ -9733,6 +9738,7 @@ components: - fastapi_apps - fastapi_root_middlewares - external_views + - react_apps - appbuilder_views - appbuilder_menu_items - global_operator_extra_links @@ -9930,6 +9936,51 @@ components: - dag_display_name title: QueuedEventResponse description: Queued Event serializer for responses.. + ReactAppResponse: + properties: + name: + type: string + title: Name + icon: + anyOf: + - type: string + - type: 'null' + title: Icon + icon_dark_mode: + anyOf: + - type: string + - type: 'null' + title: Icon Dark Mode + url_route: + anyOf: + - type: string + - type: 'null' + title: Url Route + category: + anyOf: + - type: string + - type: 'null' + title: Category + destination: + type: string + enum: + - nav + - dag + - dag_run + - task + - task_instance + title: Destination + default: nav + bundle_url: + type: string + title: Bundle Url + additionalProperties: true + type: object + required: + - name + - bundle_url + title: ReactAppResponse + description: Serializer for React App Plugin responses. ReprocessBehavior: type: string enum: diff --git a/airflow-core/src/airflow/plugins_manager.py b/airflow-core/src/airflow/plugins_manager.py index 8874864286b77..2b0b324e9e352 100644 --- a/airflow-core/src/airflow/plugins_manager.py +++ b/airflow-core/src/airflow/plugins_manager.py @@ -70,6 +70,7 @@ fastapi_apps: list[Any] | None = None fastapi_root_middlewares: list[Any] | None = None external_views: list[Any] | None = None +react_apps: list[Any] | None = None menu_links: list[Any] | None = None flask_appbuilder_views: list[Any] | None = None flask_appbuilder_menu_links: list[Any] | None = None @@ -92,6 +93,7 @@ "fastapi_apps", "fastapi_root_middlewares", "external_views", + "react_apps", "menu_links", "appbuilder_views", "appbuilder_menu_items", @@ -157,6 +159,7 @@ class AirflowPlugin: fastapi_apps: list[Any] = [] fastapi_root_middlewares: list[Any] = [] external_views: list[Any] = [] + react_apps: list[Any] = [] menu_links: list[Any] = [] appbuilder_views: list[Any] = [] appbuilder_menu_items: list[Any] = [] @@ -372,8 +375,9 @@ def initialize_ui_plugins(): """Collect extension points for the UI.""" global plugins global external_views + global react_apps - if external_views is not None: + if external_views is not None and react_apps is not None: return ensure_plugins_loaded() @@ -384,9 +388,11 @@ def initialize_ui_plugins(): log.debug("Initialize UI plugin") external_views = [] + react_apps = [] for plugin in plugins: external_views.extend(plugin.external_views) + react_apps.extend(plugin.react_apps) def initialize_flask_plugins(): diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts index 6f5f33136e54f..22c1afe36ce6e 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts @@ -3268,10 +3268,6 @@ export const $ExternalViewResponse = { type: 'string', title: 'Name' }, - href: { - type: 'string', - title: 'Href' - }, icon: { anyOf: [ { @@ -3321,13 +3317,17 @@ export const $ExternalViewResponse = { enum: ['nav', 'dag', 'dag_run', 'task', 'task_instance'], title: 'Destination', default: 'nav' + }, + href: { + type: 'string', + title: 'Href' } }, additionalProperties: true, type: 'object', required: ['name', 'href'], title: 'ExternalViewResponse', - description: 'Serializer for IFrame Plugin responses.' + description: 'Serializer for External View Plugin responses.' } as const; export const $ExtraLinkCollectionResponse = { @@ -3847,6 +3847,13 @@ export const $PluginResponse = { title: 'External Views', description: "Aggregate all external views. Both 'external_views' and 'appbuilder_menu_items' are included here." }, + react_apps: { + items: { + '$ref': '#/components/schemas/ReactAppResponse' + }, + type: 'array', + title: 'React Apps' + }, appbuilder_views: { items: { '$ref': '#/components/schemas/AppBuilderViewResponse' @@ -3896,7 +3903,7 @@ export const $PluginResponse = { } }, type: 'object', - required: ['name', 'macros', 'flask_blueprints', 'fastapi_apps', 'fastapi_root_middlewares', 'external_views', 'appbuilder_views', 'appbuilder_menu_items', 'global_operator_extra_links', 'operator_extra_links', 'source', 'listeners', 'timetables'], + required: ['name', 'macros', 'flask_blueprints', 'fastapi_apps', 'fastapi_root_middlewares', 'external_views', 'react_apps', 'appbuilder_views', 'appbuilder_menu_items', 'global_operator_extra_links', 'operator_extra_links', 'source', 'listeners', 'timetables'], title: 'PluginResponse', description: 'Plugin serializer.' } as const; @@ -4152,6 +4159,74 @@ export const $QueuedEventResponse = { description: 'Queued Event serializer for responses..' } as const; +export const $ReactAppResponse = { + properties: { + name: { + type: 'string', + title: 'Name' + }, + icon: { + anyOf: [ + { + type: 'string' + }, + { + type: 'null' + } + ], + title: 'Icon' + }, + icon_dark_mode: { + anyOf: [ + { + type: 'string' + }, + { + type: 'null' + } + ], + title: 'Icon Dark Mode' + }, + url_route: { + anyOf: [ + { + type: 'string' + }, + { + type: 'null' + } + ], + title: 'Url Route' + }, + category: { + anyOf: [ + { + type: 'string' + }, + { + type: 'null' + } + ], + title: 'Category' + }, + destination: { + type: 'string', + enum: ['nav', 'dag', 'dag_run', 'task', 'task_instance'], + title: 'Destination', + default: 'nav' + }, + bundle_url: { + type: 'string', + title: 'Bundle Url' + } + }, + additionalProperties: true, + type: 'object', + required: ['name', 'bundle_url'], + title: 'ReactAppResponse', + description: 'Serializer for React App Plugin responses.' +} as const; + export const $ReprocessBehavior = { type: 'string', enum: ['failed', 'completed', 'none'], diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts index 692a290abccfe..25c2e9b86a7ef 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts @@ -872,16 +872,16 @@ export type ExternalLogUrlResponse = { }; /** - * Serializer for IFrame Plugin responses. + * Serializer for External View Plugin responses. */ export type ExternalViewResponse = { name: string; - href: string; icon?: string | null; icon_dark_mode?: string | null; url_route?: string | null; category?: string | null; destination?: 'nav' | 'dag' | 'dag_run' | 'task' | 'task_instance'; + href: string; [key: string]: unknown | string; }; @@ -1042,6 +1042,7 @@ export type PluginResponse = { * Aggregate all external views. Both 'external_views' and 'appbuilder_menu_items' are included here. */ external_views: Array; + react_apps: Array; appbuilder_views: Array; /** * @deprecated @@ -1133,6 +1134,20 @@ export type QueuedEventResponse = { dag_display_name: string; }; +/** + * Serializer for React App Plugin responses. + */ +export type ReactAppResponse = { + name: string; + icon?: string | null; + icon_dark_mode?: string | null; + url_route?: string | null; + category?: string | null; + destination?: 'nav' | 'dag' | 'dag_run' | 'task' | 'task_instance'; + bundle_url: string; + [key: string]: unknown | string; +}; + /** * Internal enum for setting reprocess behavior in a backfill. * diff --git a/airflow-core/tests/unit/cli/commands/test_plugins_command.py b/airflow-core/tests/unit/cli/commands/test_plugins_command.py index 885a8b2510fe6..c6362935c1315 100644 --- a/airflow-core/tests/unit/cli/commands/test_plugins_command.py +++ b/airflow-core/tests/unit/cli/commands/test_plugins_command.py @@ -100,6 +100,16 @@ def test_should_display_one_plugin(self): "category": "browse", }, ], + "react_apps": [ + { + "name": "Test React App", + "bundle_url": "https://example.com/test-plugin-bundle.js", + "icon": "https://raw.githubusercontent.com/lucide-icons/lucide/refs/heads/main/icons/plug.svg", + "url_route": "test_react_app", + "destination": "nav", + "category": "browse", + } + ], "appbuilder_views": [ { "name": "Test View", diff --git a/airflow-core/tests/unit/plugins/test_plugin.py b/airflow-core/tests/unit/plugins/test_plugin.py index 7901e1cc99b55..c4c1fac515016 100644 --- a/airflow-core/tests/unit/plugins/test_plugin.py +++ b/airflow-core/tests/unit/plugins/test_plugin.py @@ -111,6 +111,15 @@ async def dispatch(self, request, call_next): "category": "browse", } +react_app_with_metadata = { + "name": "Test React App", + "bundle_url": "https://example.com/test-plugin-bundle.js", + "icon": "https://raw.githubusercontent.com/lucide-icons/lucide/refs/heads/main/icons/plug.svg", + "url_route": "test_react_app", + "destination": "nav", + "category": "browse", +} + # Extend an existing class to avoid the need to implement the full interface class CustomCronDataIntervalTimetable(CronDataIntervalTimetable): @@ -130,6 +139,7 @@ class AirflowTestPlugin(AirflowPlugin): fastapi_apps = [app_with_metadata] fastapi_root_middlewares = [middleware_with_metadata] external_views = [external_view_with_metadata] + react_apps = [react_app_with_metadata] appbuilder_views = [v_appbuilder_package] appbuilder_menu_items = [appbuilder_mitem, appbuilder_mitem_toplevel] global_operator_extra_links = [ diff --git a/airflow-ctl/src/airflowctl/api/datamodels/generated.py b/airflow-ctl/src/airflowctl/api/datamodels/generated.py index 86e1ba25d70f5..d09287840c67e 100644 --- a/airflow-ctl/src/airflowctl/api/datamodels/generated.py +++ b/airflow-ctl/src/airflowctl/api/datamodels/generated.py @@ -524,19 +524,19 @@ class Destination(str, Enum): class ExternalViewResponse(BaseModel): """ - Serializer for IFrame Plugin responses. + Serializer for External View Plugin responses. """ model_config = ConfigDict( extra="allow", ) name: Annotated[str, Field(title="Name")] - href: Annotated[str, Field(title="Href")] icon: Annotated[str | None, Field(title="Icon")] = None icon_dark_mode: Annotated[str | None, Field(title="Icon Dark Mode")] = None url_route: Annotated[str | None, Field(title="Url Route")] = None category: Annotated[str | None, Field(title="Category")] = None destination: Annotated[Destination | None, Field(title="Destination")] = "nav" + href: Annotated[str, Field(title="Href")] class ExtraLinkCollectionResponse(BaseModel): @@ -637,34 +637,6 @@ class PluginImportErrorResponse(BaseModel): error: Annotated[str, Field(title="Error")] -class PluginResponse(BaseModel): - """ - Plugin serializer. - """ - - name: Annotated[str, Field(title="Name")] - macros: Annotated[list[str], Field(title="Macros")] - flask_blueprints: Annotated[list[str], Field(title="Flask Blueprints")] - fastapi_apps: Annotated[list[FastAPIAppResponse], Field(title="Fastapi Apps")] - fastapi_root_middlewares: Annotated[ - list[FastAPIRootMiddlewareResponse], Field(title="Fastapi Root Middlewares") - ] - external_views: Annotated[ - list[ExternalViewResponse], - Field( - description="Aggregate all external views. Both 'external_views' and 'appbuilder_menu_items' are included here.", - title="External Views", - ), - ] - appbuilder_views: Annotated[list[AppBuilderViewResponse], Field(title="Appbuilder Views")] - appbuilder_menu_items: Annotated[list[AppBuilderMenuItemResponse], Field(title="Appbuilder Menu Items")] - global_operator_extra_links: Annotated[list[str], Field(title="Global Operator Extra Links")] - operator_extra_links: Annotated[list[str], Field(title="Operator Extra Links")] - source: Annotated[str, Field(title="Source")] - listeners: Annotated[list[str], Field(title="Listeners")] - timetables: Annotated[list[str], Field(title="Timetables")] - - class PoolBody(BaseModel): """ Pool serializer for post bodies. @@ -731,6 +703,23 @@ class QueuedEventResponse(BaseModel): dag_display_name: Annotated[str, Field(title="Dag Display Name")] +class ReactAppResponse(BaseModel): + """ + Serializer for React App Plugin responses. + """ + + model_config = ConfigDict( + extra="allow", + ) + name: Annotated[str, Field(title="Name")] + icon: Annotated[str | None, Field(title="Icon")] = None + icon_dark_mode: Annotated[str | None, Field(title="Icon Dark Mode")] = None + url_route: Annotated[str | None, Field(title="Url Route")] = None + category: Annotated[str | None, Field(title="Category")] = None + destination: Annotated[Destination | None, Field(title="Destination")] = "nav" + bundle_url: Annotated[str, Field(title="Bundle Url")] + + class ReprocessBehavior(str, Enum): """ Internal enum for setting reprocess behavior in a backfill. @@ -1484,22 +1473,42 @@ class PatchTaskInstanceBody(BaseModel): include_past: Annotated[bool | None, Field(title="Include Past")] = False -class PluginCollectionResponse(BaseModel): +class PluginImportErrorCollectionResponse(BaseModel): """ - Plugin Collection serializer. + Plugin Import Error Collection serializer. """ - plugins: Annotated[list[PluginResponse], Field(title="Plugins")] + import_errors: Annotated[list[PluginImportErrorResponse], Field(title="Import Errors")] total_entries: Annotated[int, Field(title="Total Entries")] -class PluginImportErrorCollectionResponse(BaseModel): +class PluginResponse(BaseModel): """ - Plugin Import Error Collection serializer. + Plugin serializer. """ - import_errors: Annotated[list[PluginImportErrorResponse], Field(title="Import Errors")] - total_entries: Annotated[int, Field(title="Total Entries")] + name: Annotated[str, Field(title="Name")] + macros: Annotated[list[str], Field(title="Macros")] + flask_blueprints: Annotated[list[str], Field(title="Flask Blueprints")] + fastapi_apps: Annotated[list[FastAPIAppResponse], Field(title="Fastapi Apps")] + fastapi_root_middlewares: Annotated[ + list[FastAPIRootMiddlewareResponse], Field(title="Fastapi Root Middlewares") + ] + external_views: Annotated[ + list[ExternalViewResponse], + Field( + description="Aggregate all external views. Both 'external_views' and 'appbuilder_menu_items' are included here.", + title="External Views", + ), + ] + react_apps: Annotated[list[ReactAppResponse], Field(title="React Apps")] + appbuilder_views: Annotated[list[AppBuilderViewResponse], Field(title="Appbuilder Views")] + appbuilder_menu_items: Annotated[list[AppBuilderMenuItemResponse], Field(title="Appbuilder Menu Items")] + global_operator_extra_links: Annotated[list[str], Field(title="Global Operator Extra Links")] + operator_extra_links: Annotated[list[str], Field(title="Operator Extra Links")] + source: Annotated[str, Field(title="Source")] + listeners: Annotated[list[str], Field(title="Listeners")] + timetables: Annotated[list[str], Field(title="Timetables")] class PoolCollectionResponse(BaseModel): @@ -1771,6 +1780,15 @@ class DagStatsCollectionResponse(BaseModel): total_entries: Annotated[int, Field(title="Total Entries")] +class PluginCollectionResponse(BaseModel): + """ + Plugin Collection serializer. + """ + + plugins: Annotated[list[PluginResponse], Field(title="Plugins")] + total_entries: Annotated[int, Field(title="Total Entries")] + + class TaskCollectionResponse(BaseModel): """ Task collection serializer for responses. diff --git a/devel-common/src/tests_common/test_utils/mock_plugins.py b/devel-common/src/tests_common/test_utils/mock_plugins.py index 7426df1801d11..5d2440bc2d534 100644 --- a/devel-common/src/tests_common/test_utils/mock_plugins.py +++ b/devel-common/src/tests_common/test_utils/mock_plugins.py @@ -29,6 +29,7 @@ "fastapi_apps", "fastapi_root_middlewares", "external_views", + "react_apps", "menu_links", "flask_appbuilder_views", "flask_appbuilder_menu_links", diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 36cdf13b293fa..a146fb417b2df 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -831,6 +831,7 @@ hotfix Hou howto hql +href html htmlcontent Http