This is an automated email from the ASF dual-hosted git repository.
vatsrahul1001 pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-3-test by this push:
new 3040370c4e7 [v3-3-test] Fix hierarchical_alphabetical sort order
breaking the graph and grid (#72137) (#72618)
3040370c4e7 is described below
commit 3040370c4e714dc7622859a55138c7077b8ba5e1
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Sep 8 11:45:25 2026 +0530
[v3-3-test] Fix hierarchical_alphabetical sort order breaking the graph and
grid (#72137) (#72618)
The serialized task group the UI renders only re-implemented the
topological sort order, so selecting [api] grid_view_sorting_order =
hierarchical_alphabetical called a method that does not exist on it and raised
AttributeError, failing the whole graph/grid render instead of ordering groups
and tasks alphabetically.
(cherry picked from commit adaaf3a01ecaf958b60ec696b5486e0013a9bb85)
Co-authored-by: Pierre Jeambrun <[email protected]>
---
.../airflow/serialization/definitions/taskgroup.py | 14 ++++++++++
airflow-core/tests/unit/utils/test_task_group.py | 32 +++++++++++++++++++++-
2 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/airflow-core/src/airflow/serialization/definitions/taskgroup.py
b/airflow-core/src/airflow/serialization/definitions/taskgroup.py
index 0e0ae06572d..b82e1c0b933 100644
--- a/airflow-core/src/airflow/serialization/definitions/taskgroup.py
+++ b/airflow-core/src/airflow/serialization/definitions/taskgroup.py
@@ -216,6 +216,20 @@ class SerializedTaskGroup(TaskGroupMixin, DAGNode):
yield group
group = group.parent_group
+ def hierarchical_alphabetical_sort(self) -> list[DAGNode]:
+ """
+ Sort children in hierarchical alphabetical order: groups first, then
tasks, each alphabetical.
+
+ Mirrors ``TaskGroup.hierarchical_alphabetical_sort`` in task-sdk. This
orders one group's
+ direct children; the server-side graph/grid builder in
+ ``api_fastapi.core_api.services.ui.task_group`` walks the tree and
re-applies it at every
+ level, so the API response is fully ordered at all nesting levels and
the UI renders it as-is.
+ """
+ return sorted(
+ self.children.values(),
+ key=lambda node: (not isinstance(node, SerializedTaskGroup),
node.node_id),
+ )
+
def topological_sort(
self, *, group_dict: dict[str | None, SerializedTaskGroup] | None =
None
) -> list[DAGNode]:
diff --git a/airflow-core/tests/unit/utils/test_task_group.py
b/airflow-core/tests/unit/utils/test_task_group.py
index 452c302348a..35172da2368 100644
--- a/airflow-core/tests/unit/utils/test_task_group.py
+++ b/airflow-core/tests/unit/utils/test_task_group.py
@@ -20,7 +20,11 @@ from __future__ import annotations
import pendulum
import pytest
-from airflow.api_fastapi.core_api.services.ui.task_group import
task_group_to_dict, task_group_to_dict_grid
+from airflow.api_fastapi.core_api.services.ui.task_group import (
+ get_task_group_children_getter,
+ task_group_to_dict,
+ task_group_to_dict_grid,
+)
from airflow.providers.standard.operators.bash import BashOperator
from airflow.providers.standard.operators.empty import EmptyOperator
from airflow.providers.standard.operators.python import PythonOperator
@@ -36,6 +40,7 @@ from airflow.sdk import (
from airflow.serialization.definitions.taskgroup import SerializedTaskGroup
from airflow.utils.dag_edges import dag_edges
+from tests_common.test_utils.config import conf_vars
from tests_common.test_utils.dag import create_scheduler_dag
from unit.models import DEFAULT_DATE
@@ -244,6 +249,31 @@ def test_task_group_to_dict_alternative_syntax():
assert task_group_to_dict(serialized_dag.task_group) == EXPECTED_JSON
+@conf_vars({("api", "grid_view_sorting_order"): "hierarchical_alphabetical"})
+def test_task_group_to_dict_hierarchical_alphabetical_sort():
+ """``hierarchical_alphabetical`` orders serialized children: groups first,
then tasks, each alphabetical."""
+ # The getter caches the resolved sort order, so force a re-read under the
patched config.
+ get_task_group_children_getter.cache_clear()
+ try:
+ logical_date = pendulum.parse("20200101")
+ dag = DAG("test_tg_hier_alpha", schedule=None, start_date=logical_date)
+ # Added in a non-alphabetical order, mixing tasks and groups.
+ EmptyOperator(task_id="zeta", dag=dag)
+ group_b = TaskGroup("group_b", dag=dag)
+ EmptyOperator(task_id="b1", dag=dag, task_group=group_b)
+ group_a = TaskGroup("group_a", dag=dag)
+ EmptyOperator(task_id="a1", dag=dag, task_group=group_a)
+ EmptyOperator(task_id="alpha", dag=dag)
+
+ serialized_dag = create_scheduler_dag(dag)
+ node = task_group_to_dict(serialized_dag.task_group)
+
+ assert [child["id"] for child in node["children"]] == ["group_a",
"group_b", "alpha", "zeta"]
+ finally:
+ # Don't leak the patched sort order to other tests.
+ get_task_group_children_getter.cache_clear()
+
+
def test_task_group_to_dict_builds_group_dict_once(monkeypatch):
"""Rendering the whole tree threads one group_dict; it is not rebuilt per
nested group."""
with DAG("test_group_dict_once", schedule=None, start_date=DEFAULT_DATE)
as dag: