This is an automated email from the ASF dual-hosted git repository.
kaxil pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new b1ce1cac7f5 Fix Common AI Model tab not loading on Airflow 3.4 (#74898)
b1ce1cac7f5 is described below
commit b1ce1cac7f53aae9385637dad8e24ffa159876d4
Author: Kaxil Naik <[email protected]>
AuthorDate: Sat Oct 17 13:54:48 2026 +0100
Fix Common AI Model tab not loading on Airflow 3.4 (#74898)
The AI Model React app scoped itself with an 'operator_names' applies_to
key, which names no TaskInstanceResponse field. Since core started
rejecting such paths, the api-server withholds the app and the tab never
appears. Use the 'operator_name' field instead.
---
.../airflow/providers/common/ai/plugins/model_panel.py | 2 +-
.../tests/unit/common/ai/plugins/test_model_panel.py | 18 +++++++++++++++++-
2 files changed, 18 insertions(+), 2 deletions(-)
diff --git
a/providers/common/ai/src/airflow/providers/common/ai/plugins/model_panel.py
b/providers/common/ai/src/airflow/providers/common/ai/plugins/model_panel.py
index 966cd3acc4f..f3a4295cbf7 100644
--- a/providers/common/ai/src/airflow/providers/common/ai/plugins/model_panel.py
+++ b/providers/common/ai/src/airflow/providers/common/ai/plugins/model_panel.py
@@ -86,7 +86,7 @@ class ModelPanelPlugin(AirflowPlugin):
# class name and the decorator's display name, since
TaskInstance falls
# back to the class name when there's no custom_operator_name.
"applies_to": {
- "operator_names": [
+ "operator_name": [
"LLMOperator",
"AgentOperator",
"@task.llm",
diff --git
a/providers/common/ai/tests/unit/common/ai/plugins/test_model_panel.py
b/providers/common/ai/tests/unit/common/ai/plugins/test_model_panel.py
index 8c27a43cd70..94c0363da14 100644
--- a/providers/common/ai/tests/unit/common/ai/plugins/test_model_panel.py
+++ b/providers/common/ai/tests/unit/common/ai/plugins/test_model_panel.py
@@ -16,13 +16,17 @@
# under the License.
from __future__ import annotations
+import copy
+
import pytest
+from tests_common.test_utils.mock_plugins import mock_plugin_manager
from tests_common.test_utils.version_compat import AIRFLOW_V_3_1_PLUS,
AIRFLOW_V_3_4_PLUS
if not AIRFLOW_V_3_1_PLUS:
pytest.skip("The AI Model panel is only compatible with Airflow >= 3.1.0",
allow_module_level=True)
+from airflow import plugins_manager
from airflow.providers.common.ai.decorators.agent import
_AgentDecoratedOperator
from airflow.providers.common.ai.decorators.llm import _LLMDecoratedOperator
from airflow.providers.common.ai.operators.agent import AgentOperator
@@ -63,10 +67,22 @@ class TestModelPanelPlugin:
"""Tied to the actual operator/decorator names a task instance would
record,
so a rename of either doesn't silently desync the tab's scoping from
reality."""
app = ModelPanelPlugin.react_apps[0]
- operator_names = app["applies_to"]["operator_names"]
+ operator_names = app["applies_to"]["operator_name"]
assert set(operator_names) == {
LLMOperator.__name__,
AgentOperator.__name__,
_LLMDecoratedOperator.custom_operator_name,
_AgentDecoratedOperator.custom_operator_name,
}
+
+ @requires_3_4
+ def test_react_app_survives_core_applies_to_validation(self):
+ """Core withholds a React App whose applies_to path names no
TaskInstanceResponse field."""
+ plugin = ModelPanelPlugin()
+ # Core rewrites each view's applies_to in place, so validate a copy,
not the class-level list.
+ plugin.react_apps = copy.deepcopy(ModelPanelPlugin.react_apps)
+
+ with mock_plugin_manager(plugins=[plugin]):
+ _, react_apps = plugins_manager._get_ui_plugins()
+
+ assert [app["name"] for app in react_apps] == ["AI Model"]