This is an automated email from the ASF dual-hosted git repository.

pierrejeambrun 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 5d6d98d274b AIP-84 Enable multi sorting (#53408)
5d6d98d274b is described below

commit 5d6d98d274bfe9618ad2f6eb1ccb35ecc784f326
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Fri Jul 25 16:36:22 2025 +0200

    AIP-84 Enable multi sorting (#53408)
    
    * AIP-84 support multiple sorting criterias
    
    * Fix CI
    
    * Update airflow-core/src/airflow/api_fastapi/common/parameters.py
    
    * Fix front-end
    
    * Fix CI, add test
---
 .../src/airflow/api_fastapi/common/db/dags.py      |  12 +-
 .../src/airflow/api_fastapi/common/parameters.py   |  72 ++++++++----
 .../api_fastapi/core_api/openapi/_private_ui.yaml  |  28 +++--
 .../core_api/openapi/v2-rest-api-generated.yaml    | 126 +++++++++++++++------
 .../api_fastapi/core_api/routes/public/dag_run.py  |   2 +-
 .../core_api/routes/public/task_instances.py       |   2 +-
 .../airflow/api_fastapi/core_api/routes/ui/grid.py |  10 +-
 .../src/airflow/ui/openapi-gen/queries/common.ts   |  44 +++----
 .../ui/openapi-gen/queries/ensureQueryData.ts      |  44 +++----
 .../src/airflow/ui/openapi-gen/queries/prefetch.ts |  44 +++----
 .../src/airflow/ui/openapi-gen/queries/queries.ts  |  44 +++----
 .../src/airflow/ui/openapi-gen/queries/suspense.ts |  44 +++----
 .../airflow/ui/openapi-gen/requests/types.gen.ts   |  44 +++----
 .../airflow/ui/src/components/DagVersionSelect.tsx |   2 +-
 .../src/airflow/ui/src/pages/Asset/AssetLayout.tsx |   2 +-
 .../airflow/ui/src/pages/AssetsList/AssetsList.tsx |   2 +-
 .../ui/src/pages/Connections/Connections.tsx       |   2 +-
 .../airflow/ui/src/pages/Dag/Overview/Overview.tsx |   4 +-
 .../airflow/ui/src/pages/Dag/Tasks/TaskCard.tsx    |   2 +-
 airflow-core/src/airflow/ui/src/pages/DagRuns.tsx  |   2 +-
 .../src/pages/DagsList/DagsFilters/DagsFilters.tsx |   2 +-
 .../src/airflow/ui/src/pages/DagsList/DagsList.tsx |   2 +-
 .../HistoricalMetrics/HistoricalMetrics.tsx        |   2 +-
 .../src/airflow/ui/src/pages/Events/Events.tsx     |   2 +-
 .../src/airflow/ui/src/pages/Pools/Pools.tsx       |   2 +-
 .../ui/src/pages/Task/Overview/Overview.tsx        |   2 +-
 .../ui/src/pages/TaskInstances/TaskInstances.tsx   |   2 +-
 .../airflow/ui/src/pages/Variables/Variables.tsx   |   2 +-
 .../airflow/ui/src/queries/useDagTagsInfinite.ts   |   2 +-
 .../src/airflow/ui/src/queries/useDags.tsx         |   2 +-
 .../src/airflow/ui/src/queries/useGridRuns.ts      |   2 +-
 .../src/airflow/ui/src/queries/useGridStructure.ts |   2 +-
 .../tests/unit/api_fastapi/common/db/test_dags.py  |  16 ++-
 .../unit/api_fastapi/common/test_parameters.py     |  40 +++++++
 .../core_api/routes/public/test_dags.py            |   5 +
 35 files changed, 379 insertions(+), 238 deletions(-)

diff --git a/airflow-core/src/airflow/api_fastapi/common/db/dags.py 
b/airflow-core/src/airflow/api_fastapi/common/db/dags.py
index 36283f86c55..cefce662798 100644
--- a/airflow-core/src/airflow/api_fastapi/common/db/dags.py
+++ b/airflow-core/src/airflow/api_fastapi/common/db/dags.py
@@ -52,12 +52,12 @@ def generate_dag_with_latest_run_query(max_run_filters: 
list[BaseParam], order_b
             has_max_run_filter = True
             break
 
-    if has_max_run_filter or order_by.value in (
-        "last_run_state",
-        "last_run_start_date",
-        "-last_run_state",
-        "-last_run_start_date",
-    ):
+    requested_order_by_set = set(order_by.value) if order_by.value is not None 
else set()
+    dag_run_order_by_set = set(
+        ["last_run_state", "last_run_start_date", "-last_run_state", 
"-last_run_start_date"],
+    )
+
+    if has_max_run_filter or (requested_order_by_set & dag_run_order_by_set):
         query = query.join(
             max_run_id_query,
             DagModel.dag_id == max_run_id_query.c.dag_id,
diff --git a/airflow-core/src/airflow/api_fastapi/common/parameters.py 
b/airflow-core/src/airflow/api_fastapi/common/parameters.py
index e8af5380f8a..bd3534c6217 100644
--- a/airflow-core/src/airflow/api_fastapi/common/parameters.py
+++ b/airflow-core/src/airflow/api_fastapi/common/parameters.py
@@ -198,9 +198,11 @@ def search_param_factory(
     return depends_search
 
 
-class SortParam(BaseParam[str]):
+class SortParam(BaseParam[list[str]]):
     """Order result by the attribute."""
 
+    MAX_SORT_PARAMS = 10
+
     def __init__(
         self, allowed_attrs: list[str], model: Base, to_replace: dict[str, str 
| Column] | None = None
     ) -> None:
@@ -214,38 +216,56 @@ class SortParam(BaseParam[str]):
             raise ValueError(f"Cannot set 'skip_none' to False on a 
{type(self)}")
 
         if self.value is None:
-            self.value = self.get_primary_key_string()
-
-        lstriped_orderby = self.value.lstrip("-")
-        column: Column | None = None
-        if self.to_replace:
-            replacement = self.to_replace.get(lstriped_orderby, 
lstriped_orderby)
-            if isinstance(replacement, str):
-                lstriped_orderby = replacement
-            else:
-                column = replacement
+            self.value = [self.get_primary_key_string()]
 
-        if (self.allowed_attrs and lstriped_orderby not in self.allowed_attrs) 
and column is None:
+        order_by_values = self.value
+        if len(order_by_values) > self.MAX_SORT_PARAMS:
             raise HTTPException(
                 400,
-                f"Ordering with '{lstriped_orderby}' is disallowed or "
-                f"the attribute does not exist on the model",
+                f"Ordering with more than {self.MAX_SORT_PARAMS} parameters is 
not allowed. Provided: {order_by_values}",
             )
-        if column is None:
-            column = getattr(self.model, lstriped_orderby)
 
-        # MySQL does not support `nullslast`, and True/False ordering depends 
on the
-        # database implementation.
-        nullscheck = case((column.isnot(None), 0), else_=1)
+        columns: list[Column] = []
+        for order_by_value in order_by_values:
+            lstriped_orderby = order_by_value.lstrip("-")
+            column: Column | None = None
+            if self.to_replace:
+                replacement = self.to_replace.get(lstriped_orderby, 
lstriped_orderby)
+                if isinstance(replacement, str):
+                    lstriped_orderby = replacement
+                else:
+                    column = replacement
+
+            if (self.allowed_attrs and lstriped_orderby not in 
self.allowed_attrs) and column is None:
+                raise HTTPException(
+                    400,
+                    f"Ordering with '{lstriped_orderby}' is disallowed or "
+                    f"the attribute does not exist on the model",
+                )
+            if column is None:
+                column = getattr(self.model, lstriped_orderby)
+
+            # MySQL does not support `nullslast`, and True/False ordering 
depends on the
+            # database implementation.
+            nullscheck = case((column.isnot(None), 0), else_=1)
+
+            columns.append(nullscheck)
+            if order_by_value.startswith("-"):
+                columns.append(column.desc())
+            else:
+                columns.append(column.asc())
 
         # Reset default sorting
         select = select.order_by(None)
 
         primary_key_column = self.get_primary_key_column()
+        # Always add a final discriminator to enforce deterministic ordering.
+        if order_by_values and order_by_values[0].startswith("-"):
+            columns.append(primary_key_column.desc())
+        else:
+            columns.append(primary_key_column.asc())
 
-        if self.value[0] == "-":
-            return select.order_by(nullscheck, column.desc(), 
primary_key_column.desc())
-        return select.order_by(nullscheck, column.asc(), 
primary_key_column.asc())
+        return select.order_by(*columns)
 
     def get_primary_key_column(self) -> Column:
         """Get the primary key column of the model of SortParam object."""
@@ -260,8 +280,12 @@ class SortParam(BaseParam[str]):
         raise NotImplementedError("Use dynamic_depends, depends not 
implemented.")
 
     def dynamic_depends(self, default: str | None = None) -> Callable:
-        def inner(order_by: str = default or self.get_primary_key_string()) -> 
SortParam:
-            return self.set_value(self.get_primary_key_string() if order_by == 
"" else order_by)
+        def inner(
+            order_by: list[str] = Query(
+                default=[default] if default is not None else 
[self.get_primary_key_string()]
+            ),
+        ) -> SortParam:
+            return self.set_value(order_by)
 
         return inner
 
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml 
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
index 4abbae3e238..f8289bba786 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
+++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml
@@ -220,8 +220,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: dag_id
+          type: array
+          items:
+            type: string
+          default:
+          - dag_id
           title: Order By
       - name: is_favorite
         in: query
@@ -485,8 +488,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: id
+          type: array
+          items:
+            type: string
+          default:
+          - id
           title: Order By
       - name: dag_id
         in: query
@@ -560,8 +566,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: id
+          type: array
+          items:
+            type: string
+          default:
+          - id
           title: Order By
       - name: run_after_gte
         in: query
@@ -646,8 +655,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: id
+          type: array
+          items:
+            type: string
+          default:
+          - id
           title: Order By
       - name: run_after_gte
         in: query
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 1bbb151cc14..2378ec76f73 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
@@ -77,8 +77,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: id
+          type: array
+          items:
+            type: string
+          default:
+          - id
           title: Order By
       responses:
         '200':
@@ -154,8 +157,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: id
+          type: array
+          items:
+            type: string
+          default:
+          - id
           title: Order By
       responses:
         '200':
@@ -266,8 +272,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: timestamp
+          type: array
+          items:
+            type: string
+          default:
+          - timestamp
           title: Order By
       - name: asset_id
         in: query
@@ -886,8 +895,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: id
+          type: array
+          items:
+            type: string
+          default:
+          - id
           title: Order By
       responses:
         '200':
@@ -1418,8 +1430,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: id
+          type: array
+          items:
+            type: string
+          default:
+          - id
           title: Order By
       - name: connection_id_pattern
         in: query
@@ -2072,8 +2087,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: id
+          type: array
+          items:
+            type: string
+          default:
+          - id
           title: Order By
       - name: run_id_pattern
         in: query
@@ -2718,8 +2736,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: dag_id
+          type: array
+          items:
+            type: string
+          default:
+          - dag_id
           title: Order By
       responses:
         '200':
@@ -2895,8 +2916,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: dag_id
+          type: array
+          items:
+            type: string
+          default:
+          - dag_id
           title: Order By
       - name: is_favorite
         in: query
@@ -3465,8 +3489,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: id
+          type: array
+          items:
+            type: string
+          default:
+          - id
           title: Order By
       - name: dag_id
         in: query
@@ -3734,8 +3761,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: id
+          type: array
+          items:
+            type: string
+          default:
+          - id
           title: Order By
       responses:
         '200':
@@ -3837,8 +3867,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: id
+          type: array
+          items:
+            type: string
+          default:
+          - id
           title: Order By
       - name: job_state
         in: query
@@ -4179,8 +4212,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: id
+          type: array
+          items:
+            type: string
+          default:
+          - id
           title: Order By
       - name: pool_name_pattern
         in: query
@@ -5128,8 +5164,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: map_index
+          type: array
+          items:
+            type: string
+          default:
+          - map_index
           title: Order By
       responses:
         '200':
@@ -5803,8 +5842,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: map_index
+          type: array
+          items:
+            type: string
+          default:
+          - map_index
           title: Order By
       responses:
         '200':
@@ -6641,8 +6683,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: id
+          type: array
+          items:
+            type: string
+          default:
+          - id
           title: Order By
       - name: variable_key_pattern
         in: query
@@ -7035,8 +7080,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: name
+          type: array
+          items:
+            type: string
+          default:
+          - name
           title: Order By
       - name: tag_name_pattern
         in: query
@@ -7190,8 +7238,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: id
+          type: array
+          items:
+            type: string
+          default:
+          - id
           title: Order By
       responses:
         '200':
@@ -7529,8 +7580,11 @@ paths:
         in: query
         required: false
         schema:
-          type: string
-          default: ti_id
+          type: array
+          items:
+            type: string
+          default:
+          - ti_id
           title: Order By
       - name: dag_id_pattern
         in: query
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dag_run.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dag_run.py
index 00f93754baf..643ee1e528f 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dag_run.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/dag_run.py
@@ -544,7 +544,7 @@ def get_list_dag_runs_batch(
         ],
         DagRun,
         {"dag_run_id": "run_id"},
-    ).set_value(body.order_by)
+    ).set_value([body.order_by] if body.order_by else None)
 
     base_query = select(DagRun).options(joinedload(DagRun.dag_model))
     dag_runs_select, total_entries = paginated_select(
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/task_instances.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/task_instances.py
index 33bafc0506b..07885a7a667 100644
--- 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/task_instances.py
+++ 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/task_instances.py
@@ -541,7 +541,7 @@ def get_task_instances_batch(
     order_by = SortParam(
         ["id", "state", "duration", "start_date", "end_date", "map_index"],
         TI,
-    ).set_value(body.order_by)
+    ).set_value([body.order_by] if body.order_by else None)
 
     query = select(TI).join(TI.dag_run)
     task_instance_select, total_entries = paginated_select(
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
index f4c6d7d558c..80e68bd890f 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
@@ -127,12 +127,12 @@ def get_dag_structure(
     # Retrieve, sort the previous DAG Runs
     base_query = select(DagRun.id).where(DagRun.dag_id == dag_id)
     # This comparison is to fall back to DAG timetable when no order_by is 
provided
-    if order_by.value == order_by.get_primary_key_string():
+    if order_by.value == [order_by.get_primary_key_string()]:
         ordering = list(latest_dag.timetable.run_ordering)
         order_by = SortParam(
             allowed_attrs=ordering,
             model=DagRun,
-        ).set_value(ordering[0])
+        ).set_value(ordering)
     dag_runs_select_filter, _ = paginated_select(
         statement=base_query,
         order_by=order_by,
@@ -230,14 +230,14 @@ def get_grid_runs(
     ).where(DagRun.dag_id == dag_id)
 
     # This comparison is to fall back to DAG timetable when no order_by is 
provided
-    if order_by.value == order_by.get_primary_key_string():
+    if order_by.value == [order_by.get_primary_key_string()]:
         latest_serdag = _get_latest_serdag(dag_id, session)
         latest_dag = latest_serdag.dag
         ordering = list(latest_dag.timetable.run_ordering)
         order_by = SortParam(
             allowed_attrs=ordering,
             model=DagRun,
-        ).set_value(ordering[0])
+        ).set_value(ordering)
     dag_runs_select_filter, _ = paginated_select(
         statement=base_query,
         order_by=order_by,
@@ -305,7 +305,7 @@ def get_grid_ti_summaries(
             )
         ),
         filters=[],
-        order_by=SortParam(allowed_attrs=["task_id", "run_id"], 
model=TaskInstance).set_value("task_id"),
+        order_by=SortParam(allowed_attrs=["task_id", "run_id"], 
model=TaskInstance).set_value(["task_id"]),
         limit=None,
         return_total_entries=False,
     )
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
index 837eca5e85d..2fefc3d5f53 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -12,7 +12,7 @@ export const UseAssetServiceGetAssetsKeyFn = ({ dagIds, 
limit, namePattern, offs
   namePattern?: string;
   offset?: number;
   onlyActive?: boolean;
-  orderBy?: string;
+  orderBy?: string[];
   uriPattern?: string;
 } = {}, queryKey?: Array<unknown>) => [useAssetServiceGetAssetsKey, 
...(queryKey ?? [{ dagIds, limit, namePattern, offset, onlyActive, orderBy, 
uriPattern }])];
 export type AssetServiceGetAssetAliasesDefaultResponse = 
Awaited<ReturnType<typeof AssetService.getAssetAliases>>;
@@ -22,7 +22,7 @@ export const UseAssetServiceGetAssetAliasesKeyFn = ({ limit, 
namePattern, offset
   limit?: number;
   namePattern?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}, queryKey?: Array<unknown>) => [useAssetServiceGetAssetAliasesKey, 
...(queryKey ?? [{ limit, namePattern, offset, orderBy }])];
 export type AssetServiceGetAssetAliasDefaultResponse = 
Awaited<ReturnType<typeof AssetService.getAssetAlias>>;
 export type AssetServiceGetAssetAliasQueryResult<TData = 
AssetServiceGetAssetAliasDefaultResponse, TError = unknown> = 
UseQueryResult<TData, TError>;
@@ -37,7 +37,7 @@ export const UseAssetServiceGetAssetEventsKeyFn = ({ assetId, 
limit, offset, ord
   assetId?: number;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   sourceDagId?: string;
   sourceMapIndex?: number;
   sourceRunId?: string;
@@ -86,7 +86,7 @@ export const UseBackfillServiceListBackfillsKeyFn = ({ dagId, 
limit, offset, ord
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 }, queryKey?: Array<unknown>) => [useBackfillServiceListBackfillsKey, 
...(queryKey ?? [{ dagId, limit, offset, orderBy }])];
 export type BackfillServiceGetBackfillDefaultResponse = 
Awaited<ReturnType<typeof BackfillService.getBackfill>>;
 export type BackfillServiceGetBackfillQueryResult<TData = 
BackfillServiceGetBackfillDefaultResponse, TError = unknown> = 
UseQueryResult<TData, TError>;
@@ -102,7 +102,7 @@ export const UseBackfillServiceListBackfillsUiKeyFn = ({ 
active, dagId, limit, o
   dagId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}, queryKey?: Array<unknown>) => [useBackfillServiceListBackfillsUiKey, 
...(queryKey ?? [{ active, dagId, limit, offset, orderBy }])];
 export type ConnectionServiceGetConnectionDefaultResponse = 
Awaited<ReturnType<typeof ConnectionService.getConnection>>;
 export type ConnectionServiceGetConnectionQueryResult<TData = 
ConnectionServiceGetConnectionDefaultResponse, TError = unknown> = 
UseQueryResult<TData, TError>;
@@ -117,7 +117,7 @@ export const UseConnectionServiceGetConnectionsKeyFn = ({ 
connectionIdPattern, l
   connectionIdPattern?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}, queryKey?: Array<unknown>) => [useConnectionServiceGetConnectionsKey, 
...(queryKey ?? [{ connectionIdPattern, limit, offset, orderBy }])];
 export type ConnectionServiceHookMetaDataDefaultResponse = 
Awaited<ReturnType<typeof ConnectionService.hookMetaData>>;
 export type ConnectionServiceHookMetaDataQueryResult<TData = 
ConnectionServiceHookMetaDataDefaultResponse, TError = unknown> = 
UseQueryResult<TData, TError>;
@@ -148,7 +148,7 @@ export const UseDagRunServiceGetDagRunsKeyFn = ({ dagId, 
endDateGte, endDateLte,
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
   runIdPattern?: string;
@@ -223,7 +223,7 @@ export const UseDagWarningServiceListDagWarningsKeyFn = ({ 
dagId, limit, offset,
   dagId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   warningType?: DagWarningType;
 } = {}, queryKey?: Array<unknown>) => [useDagWarningServiceListDagWarningsKey, 
...(queryKey ?? [{ dagId, limit, offset, orderBy, warningType }])];
 export type DagServiceGetDagsDefaultResponse = Awaited<ReturnType<typeof 
DagService.getDags>>;
@@ -242,7 +242,7 @@ export const UseDagServiceGetDagsKeyFn = ({ 
dagDisplayNamePattern, dagIdPattern,
   lastDagRunState?: DagRunState;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owners?: string[];
   paused?: boolean;
   tags?: string[];
@@ -266,7 +266,7 @@ export const useDagServiceGetDagTagsKey = 
"DagServiceGetDagTags";
 export const UseDagServiceGetDagTagsKeyFn = ({ limit, offset, orderBy, 
tagNamePattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   tagNamePattern?: string;
 } = {}, queryKey?: Array<unknown>) => [useDagServiceGetDagTagsKey, 
...(queryKey ?? [{ limit, offset, orderBy, tagNamePattern }])];
 export type DagServiceGetDagsUiDefaultResponse = Awaited<ReturnType<typeof 
DagService.getDagsUi>>;
@@ -282,7 +282,7 @@ export const UseDagServiceGetDagsUiKeyFn = ({ 
dagDisplayNamePattern, dagIdPatter
   lastDagRunState?: DagRunState;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owners?: string[];
   paused?: boolean;
   tags?: string[];
@@ -313,7 +313,7 @@ export const UseEventLogServiceGetEventLogsKeyFn = ({ 
after, before, dagId, even
   limit?: number;
   mapIndex?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owner?: string;
   runId?: string;
   taskId?: string;
@@ -360,7 +360,7 @@ export const 
UseTaskInstanceServiceGetMappedTaskInstancesKeyFn = ({ dagId, dagRu
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   pool?: string[];
   queue?: string[];
   runAfterGte?: string;
@@ -433,7 +433,7 @@ export const UseTaskInstanceServiceGetTaskInstancesKeyFn = 
({ dagId, dagRunId, d
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   pool?: string[];
   queue?: string[];
   runAfterGte?: string;
@@ -502,7 +502,7 @@ export const useImportErrorServiceGetImportErrorsKey = 
"ImportErrorServiceGetImp
 export const UseImportErrorServiceGetImportErrorsKeyFn = ({ limit, offset, 
orderBy }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}, queryKey?: Array<unknown>) => 
[useImportErrorServiceGetImportErrorsKey, ...(queryKey ?? [{ limit, offset, 
orderBy }])];
 export type JobServiceGetJobsDefaultResponse = Awaited<ReturnType<typeof 
JobService.getJobs>>;
 export type JobServiceGetJobsQueryResult<TData = 
JobServiceGetJobsDefaultResponse, TError = unknown> = UseQueryResult<TData, 
TError>;
@@ -517,7 +517,7 @@ export const UseJobServiceGetJobsKeyFn = ({ endDateGte, 
endDateLte, executorClas
   jobType?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   startDateGte?: string;
   startDateLte?: string;
 } = {}, queryKey?: Array<unknown>) => [useJobServiceGetJobsKey, ...(queryKey 
?? [{ endDateGte, endDateLte, executorClass, hostname, isAlive, jobState, 
jobType, limit, offset, orderBy, startDateGte, startDateLte }])];
@@ -544,7 +544,7 @@ export const usePoolServiceGetPoolsKey = 
"PoolServiceGetPools";
 export const UsePoolServiceGetPoolsKeyFn = ({ limit, offset, orderBy, 
poolNamePattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   poolNamePattern?: string;
 } = {}, queryKey?: Array<unknown>) => [usePoolServiceGetPoolsKey, ...(queryKey 
?? [{ limit, offset, orderBy, poolNamePattern }])];
 export type ProviderServiceGetProvidersDefaultResponse = 
Awaited<ReturnType<typeof ProviderService.getProviders>>;
@@ -604,7 +604,7 @@ export const useVariableServiceGetVariablesKey = 
"VariableServiceGetVariables";
 export const UseVariableServiceGetVariablesKeyFn = ({ limit, offset, orderBy, 
variableKeyPattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   variableKeyPattern?: string;
 } = {}, queryKey?: Array<unknown>) => [useVariableServiceGetVariablesKey, 
...(queryKey ?? [{ limit, offset, orderBy, variableKeyPattern }])];
 export type DagVersionServiceGetDagVersionDefaultResponse = 
Awaited<ReturnType<typeof DagVersionService.getDagVersion>>;
@@ -623,7 +623,7 @@ export const UseDagVersionServiceGetDagVersionsKeyFn = ({ 
bundleName, bundleVers
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   versionNumber?: number;
 }, queryKey?: Array<unknown>) => [useDagVersionServiceGetDagVersionsKey, 
...(queryKey ?? [{ bundleName, bundleVersion, dagId, limit, offset, orderBy, 
versionNumber }])];
 export type HumanInTheLoopServiceGetHitlDetailDefaultResponse = 
Awaited<ReturnType<typeof HumanInTheLoopService.getHitlDetail>>;
@@ -652,7 +652,7 @@ export const UseHumanInTheLoopServiceGetHitlDetailsKeyFn = 
({ bodySearch, dagIdP
   dagRunId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   responseReceived?: boolean;
   state?: string[];
   subjectSearch?: string;
@@ -717,7 +717,7 @@ export const UseGridServiceGetDagStructureKeyFn = ({ dagId, 
limit, offset, order
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
 }, queryKey?: Array<unknown>) => [useGridServiceGetDagStructureKey, 
...(queryKey ?? [{ dagId, limit, offset, orderBy, runAfterGte, runAfterLte }])];
@@ -728,7 +728,7 @@ export const UseGridServiceGetGridRunsKeyFn = ({ dagId, 
limit, offset, orderBy,
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
 }, queryKey?: Array<unknown>) => [useGridServiceGetGridRunsKey, ...(queryKey 
?? [{ dagId, limit, offset, orderBy, runAfterGte, runAfterLte }])];
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
index 210dcacb0ac..357e6988668 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -24,7 +24,7 @@ export const ensureUseAssetServiceGetAssetsData = 
(queryClient: QueryClient, { d
   namePattern?: string;
   offset?: number;
   onlyActive?: boolean;
-  orderBy?: string;
+  orderBy?: string[];
   uriPattern?: string;
 } = {}) => queryClient.ensureQueryData({ queryKey: 
Common.UseAssetServiceGetAssetsKeyFn({ dagIds, limit, namePattern, offset, 
onlyActive, orderBy, uriPattern }), queryFn: () => AssetService.getAssets({ 
dagIds, limit, namePattern, offset, onlyActive, orderBy, uriPattern }) });
 /**
@@ -42,7 +42,7 @@ export const ensureUseAssetServiceGetAssetAliasesData = 
(queryClient: QueryClien
   limit?: number;
   namePattern?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}) => queryClient.ensureQueryData({ queryKey: 
Common.UseAssetServiceGetAssetAliasesKeyFn({ limit, namePattern, offset, 
orderBy }), queryFn: () => AssetService.getAssetAliases({ limit, namePattern, 
offset, orderBy }) });
 /**
 * Get Asset Alias
@@ -76,7 +76,7 @@ export const ensureUseAssetServiceGetAssetEventsData = 
(queryClient: QueryClient
   assetId?: number;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   sourceDagId?: string;
   sourceMapIndex?: number;
   sourceRunId?: string;
@@ -160,7 +160,7 @@ export const ensureUseBackfillServiceListBackfillsData = 
(queryClient: QueryClie
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 }) => queryClient.ensureQueryData({ queryKey: 
Common.UseBackfillServiceListBackfillsKeyFn({ dagId, limit, offset, orderBy }), 
queryFn: () => BackfillService.listBackfills({ dagId, limit, offset, orderBy }) 
});
 /**
 * Get Backfill
@@ -188,7 +188,7 @@ export const ensureUseBackfillServiceListBackfillsUiData = 
(queryClient: QueryCl
   dagId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}) => queryClient.ensureQueryData({ queryKey: 
Common.UseBackfillServiceListBackfillsUiKeyFn({ active, dagId, limit, offset, 
orderBy }), queryFn: () => BackfillService.listBackfillsUi({ active, dagId, 
limit, offset, orderBy }) });
 /**
 * Get Connection
@@ -216,7 +216,7 @@ export const ensureUseConnectionServiceGetConnectionsData = 
(queryClient: QueryC
   connectionIdPattern?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}) => queryClient.ensureQueryData({ queryKey: 
Common.UseConnectionServiceGetConnectionsKeyFn({ connectionIdPattern, limit, 
offset, orderBy }), queryFn: () => ConnectionService.getConnections({ 
connectionIdPattern, limit, offset, orderBy }) });
 /**
 * Hook Meta Data
@@ -284,7 +284,7 @@ export const ensureUseDagRunServiceGetDagRunsData = 
(queryClient: QueryClient, {
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
   runIdPattern?: string;
@@ -415,7 +415,7 @@ export const ensureUseDagWarningServiceListDagWarningsData 
= (queryClient: Query
   dagId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   warningType?: DagWarningType;
 } = {}) => queryClient.ensureQueryData({ queryKey: 
Common.UseDagWarningServiceListDagWarningsKeyFn({ dagId, limit, offset, 
orderBy, warningType }), queryFn: () => DagWarningService.listDagWarnings({ 
dagId, limit, offset, orderBy, warningType }) });
 /**
@@ -455,7 +455,7 @@ export const ensureUseDagServiceGetDagsData = (queryClient: 
QueryClient, { dagDi
   lastDagRunState?: DagRunState;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owners?: string[];
   paused?: boolean;
   tags?: string[];
@@ -497,7 +497,7 @@ export const ensureUseDagServiceGetDagDetailsData = 
(queryClient: QueryClient, {
 export const ensureUseDagServiceGetDagTagsData = (queryClient: QueryClient, { 
limit, offset, orderBy, tagNamePattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   tagNamePattern?: string;
 } = {}) => queryClient.ensureQueryData({ queryKey: 
Common.UseDagServiceGetDagTagsKeyFn({ limit, offset, orderBy, tagNamePattern 
}), queryFn: () => DagService.getDagTags({ limit, offset, orderBy, 
tagNamePattern }) });
 /**
@@ -531,7 +531,7 @@ export const ensureUseDagServiceGetDagsUiData = 
(queryClient: QueryClient, { dag
   lastDagRunState?: DagRunState;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owners?: string[];
   paused?: boolean;
   tags?: string[];
@@ -589,7 +589,7 @@ export const ensureUseEventLogServiceGetEventLogsData = 
(queryClient: QueryClien
   limit?: number;
   mapIndex?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owner?: string;
   runId?: string;
   taskId?: string;
@@ -686,7 +686,7 @@ export const 
ensureUseTaskInstanceServiceGetMappedTaskInstancesData = (queryClie
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   pool?: string[];
   queue?: string[];
   runAfterGte?: string;
@@ -829,7 +829,7 @@ export const 
ensureUseTaskInstanceServiceGetTaskInstancesData = (queryClient: Qu
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   pool?: string[];
   queue?: string[];
   runAfterGte?: string;
@@ -948,7 +948,7 @@ export const ensureUseImportErrorServiceGetImportErrorData 
= (queryClient: Query
 export const ensureUseImportErrorServiceGetImportErrorsData = (queryClient: 
QueryClient, { limit, offset, orderBy }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}) => queryClient.ensureQueryData({ queryKey: 
Common.UseImportErrorServiceGetImportErrorsKeyFn({ limit, offset, orderBy }), 
queryFn: () => ImportErrorService.getImportErrors({ limit, offset, orderBy }) 
});
 /**
 * Get Jobs
@@ -979,7 +979,7 @@ export const ensureUseJobServiceGetJobsData = (queryClient: 
QueryClient, { endDa
   jobType?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   startDateGte?: string;
   startDateLte?: string;
 } = {}) => queryClient.ensureQueryData({ queryKey: 
Common.UseJobServiceGetJobsKeyFn({ endDateGte, endDateLte, executorClass, 
hostname, isAlive, jobState, jobType, limit, offset, orderBy, startDateGte, 
startDateLte }), queryFn: () => JobService.getJobs({ endDateGte, endDateLte, 
executorClass, hostname, isAlive, jobState, jobType, limit, offset, orderBy, 
startDateGte, startDateLte }) });
@@ -1026,7 +1026,7 @@ export const ensureUsePoolServiceGetPoolData = 
(queryClient: QueryClient, { pool
 export const ensureUsePoolServiceGetPoolsData = (queryClient: QueryClient, { 
limit, offset, orderBy, poolNamePattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   poolNamePattern?: string;
 } = {}) => queryClient.ensureQueryData({ queryKey: 
Common.UsePoolServiceGetPoolsKeyFn({ limit, offset, orderBy, poolNamePattern 
}), queryFn: () => PoolService.getPools({ limit, offset, orderBy, 
poolNamePattern }) });
 /**
@@ -1141,7 +1141,7 @@ export const ensureUseVariableServiceGetVariableData = 
(queryClient: QueryClient
 export const ensureUseVariableServiceGetVariablesData = (queryClient: 
QueryClient, { limit, offset, orderBy, variableKeyPattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   variableKeyPattern?: string;
 } = {}) => queryClient.ensureQueryData({ queryKey: 
Common.UseVariableServiceGetVariablesKeyFn({ limit, offset, orderBy, 
variableKeyPattern }), queryFn: () => VariableService.getVariables({ limit, 
offset, orderBy, variableKeyPattern }) });
 /**
@@ -1179,7 +1179,7 @@ export const ensureUseDagVersionServiceGetDagVersionsData 
= (queryClient: QueryC
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   versionNumber?: number;
 }) => queryClient.ensureQueryData({ queryKey: 
Common.UseDagVersionServiceGetDagVersionsKeyFn({ bundleName, bundleVersion, 
dagId, limit, offset, orderBy, versionNumber }), queryFn: () => 
DagVersionService.getDagVersions({ bundleName, bundleVersion, dagId, limit, 
offset, orderBy, versionNumber }) });
 /**
@@ -1237,7 +1237,7 @@ export const 
ensureUseHumanInTheLoopServiceGetHitlDetailsData = (queryClient: Qu
   dagRunId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   responseReceived?: boolean;
   state?: string[];
   subjectSearch?: string;
@@ -1353,7 +1353,7 @@ export const ensureUseGridServiceGetDagStructureData = 
(queryClient: QueryClient
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
 }) => queryClient.ensureQueryData({ queryKey: 
Common.UseGridServiceGetDagStructureKeyFn({ dagId, limit, offset, orderBy, 
runAfterGte, runAfterLte }), queryFn: () => GridService.getDagStructure({ 
dagId, limit, offset, orderBy, runAfterGte, runAfterLte }) });
@@ -1374,7 +1374,7 @@ export const ensureUseGridServiceGetGridRunsData = 
(queryClient: QueryClient, {
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
 }) => queryClient.ensureQueryData({ queryKey: 
Common.UseGridServiceGetGridRunsKeyFn({ dagId, limit, offset, orderBy, 
runAfterGte, runAfterLte }), queryFn: () => GridService.getGridRuns({ dagId, 
limit, offset, orderBy, runAfterGte, runAfterLte }) });
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
index bf2a3841c2e..3d8009ce490 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -24,7 +24,7 @@ export const prefetchUseAssetServiceGetAssets = (queryClient: 
QueryClient, { dag
   namePattern?: string;
   offset?: number;
   onlyActive?: boolean;
-  orderBy?: string;
+  orderBy?: string[];
   uriPattern?: string;
 } = {}) => queryClient.prefetchQuery({ queryKey: 
Common.UseAssetServiceGetAssetsKeyFn({ dagIds, limit, namePattern, offset, 
onlyActive, orderBy, uriPattern }), queryFn: () => AssetService.getAssets({ 
dagIds, limit, namePattern, offset, onlyActive, orderBy, uriPattern }) });
 /**
@@ -42,7 +42,7 @@ export const prefetchUseAssetServiceGetAssetAliases = 
(queryClient: QueryClient,
   limit?: number;
   namePattern?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}) => queryClient.prefetchQuery({ queryKey: 
Common.UseAssetServiceGetAssetAliasesKeyFn({ limit, namePattern, offset, 
orderBy }), queryFn: () => AssetService.getAssetAliases({ limit, namePattern, 
offset, orderBy }) });
 /**
 * Get Asset Alias
@@ -76,7 +76,7 @@ export const prefetchUseAssetServiceGetAssetEvents = 
(queryClient: QueryClient,
   assetId?: number;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   sourceDagId?: string;
   sourceMapIndex?: number;
   sourceRunId?: string;
@@ -160,7 +160,7 @@ export const prefetchUseBackfillServiceListBackfills = 
(queryClient: QueryClient
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 }) => queryClient.prefetchQuery({ queryKey: 
Common.UseBackfillServiceListBackfillsKeyFn({ dagId, limit, offset, orderBy }), 
queryFn: () => BackfillService.listBackfills({ dagId, limit, offset, orderBy }) 
});
 /**
 * Get Backfill
@@ -188,7 +188,7 @@ export const prefetchUseBackfillServiceListBackfillsUi = 
(queryClient: QueryClie
   dagId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}) => queryClient.prefetchQuery({ queryKey: 
Common.UseBackfillServiceListBackfillsUiKeyFn({ active, dagId, limit, offset, 
orderBy }), queryFn: () => BackfillService.listBackfillsUi({ active, dagId, 
limit, offset, orderBy }) });
 /**
 * Get Connection
@@ -216,7 +216,7 @@ export const prefetchUseConnectionServiceGetConnections = 
(queryClient: QueryCli
   connectionIdPattern?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}) => queryClient.prefetchQuery({ queryKey: 
Common.UseConnectionServiceGetConnectionsKeyFn({ connectionIdPattern, limit, 
offset, orderBy }), queryFn: () => ConnectionService.getConnections({ 
connectionIdPattern, limit, offset, orderBy }) });
 /**
 * Hook Meta Data
@@ -284,7 +284,7 @@ export const prefetchUseDagRunServiceGetDagRuns = 
(queryClient: QueryClient, { d
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
   runIdPattern?: string;
@@ -415,7 +415,7 @@ export const prefetchUseDagWarningServiceListDagWarnings = 
(queryClient: QueryCl
   dagId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   warningType?: DagWarningType;
 } = {}) => queryClient.prefetchQuery({ queryKey: 
Common.UseDagWarningServiceListDagWarningsKeyFn({ dagId, limit, offset, 
orderBy, warningType }), queryFn: () => DagWarningService.listDagWarnings({ 
dagId, limit, offset, orderBy, warningType }) });
 /**
@@ -455,7 +455,7 @@ export const prefetchUseDagServiceGetDags = (queryClient: 
QueryClient, { dagDisp
   lastDagRunState?: DagRunState;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owners?: string[];
   paused?: boolean;
   tags?: string[];
@@ -497,7 +497,7 @@ export const prefetchUseDagServiceGetDagDetails = 
(queryClient: QueryClient, { d
 export const prefetchUseDagServiceGetDagTags = (queryClient: QueryClient, { 
limit, offset, orderBy, tagNamePattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   tagNamePattern?: string;
 } = {}) => queryClient.prefetchQuery({ queryKey: 
Common.UseDagServiceGetDagTagsKeyFn({ limit, offset, orderBy, tagNamePattern 
}), queryFn: () => DagService.getDagTags({ limit, offset, orderBy, 
tagNamePattern }) });
 /**
@@ -531,7 +531,7 @@ export const prefetchUseDagServiceGetDagsUi = (queryClient: 
QueryClient, { dagDi
   lastDagRunState?: DagRunState;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owners?: string[];
   paused?: boolean;
   tags?: string[];
@@ -589,7 +589,7 @@ export const prefetchUseEventLogServiceGetEventLogs = 
(queryClient: QueryClient,
   limit?: number;
   mapIndex?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owner?: string;
   runId?: string;
   taskId?: string;
@@ -686,7 +686,7 @@ export const 
prefetchUseTaskInstanceServiceGetMappedTaskInstances = (queryClient
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   pool?: string[];
   queue?: string[];
   runAfterGte?: string;
@@ -829,7 +829,7 @@ export const prefetchUseTaskInstanceServiceGetTaskInstances 
= (queryClient: Quer
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   pool?: string[];
   queue?: string[];
   runAfterGte?: string;
@@ -948,7 +948,7 @@ export const prefetchUseImportErrorServiceGetImportError = 
(queryClient: QueryCl
 export const prefetchUseImportErrorServiceGetImportErrors = (queryClient: 
QueryClient, { limit, offset, orderBy }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}) => queryClient.prefetchQuery({ queryKey: 
Common.UseImportErrorServiceGetImportErrorsKeyFn({ limit, offset, orderBy }), 
queryFn: () => ImportErrorService.getImportErrors({ limit, offset, orderBy }) 
});
 /**
 * Get Jobs
@@ -979,7 +979,7 @@ export const prefetchUseJobServiceGetJobs = (queryClient: 
QueryClient, { endDate
   jobType?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   startDateGte?: string;
   startDateLte?: string;
 } = {}) => queryClient.prefetchQuery({ queryKey: 
Common.UseJobServiceGetJobsKeyFn({ endDateGte, endDateLte, executorClass, 
hostname, isAlive, jobState, jobType, limit, offset, orderBy, startDateGte, 
startDateLte }), queryFn: () => JobService.getJobs({ endDateGte, endDateLte, 
executorClass, hostname, isAlive, jobState, jobType, limit, offset, orderBy, 
startDateGte, startDateLte }) });
@@ -1026,7 +1026,7 @@ export const prefetchUsePoolServiceGetPool = 
(queryClient: QueryClient, { poolNa
 export const prefetchUsePoolServiceGetPools = (queryClient: QueryClient, { 
limit, offset, orderBy, poolNamePattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   poolNamePattern?: string;
 } = {}) => queryClient.prefetchQuery({ queryKey: 
Common.UsePoolServiceGetPoolsKeyFn({ limit, offset, orderBy, poolNamePattern 
}), queryFn: () => PoolService.getPools({ limit, offset, orderBy, 
poolNamePattern }) });
 /**
@@ -1141,7 +1141,7 @@ export const prefetchUseVariableServiceGetVariable = 
(queryClient: QueryClient,
 export const prefetchUseVariableServiceGetVariables = (queryClient: 
QueryClient, { limit, offset, orderBy, variableKeyPattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   variableKeyPattern?: string;
 } = {}) => queryClient.prefetchQuery({ queryKey: 
Common.UseVariableServiceGetVariablesKeyFn({ limit, offset, orderBy, 
variableKeyPattern }), queryFn: () => VariableService.getVariables({ limit, 
offset, orderBy, variableKeyPattern }) });
 /**
@@ -1179,7 +1179,7 @@ export const prefetchUseDagVersionServiceGetDagVersions = 
(queryClient: QueryCli
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   versionNumber?: number;
 }) => queryClient.prefetchQuery({ queryKey: 
Common.UseDagVersionServiceGetDagVersionsKeyFn({ bundleName, bundleVersion, 
dagId, limit, offset, orderBy, versionNumber }), queryFn: () => 
DagVersionService.getDagVersions({ bundleName, bundleVersion, dagId, limit, 
offset, orderBy, versionNumber }) });
 /**
@@ -1237,7 +1237,7 @@ export const 
prefetchUseHumanInTheLoopServiceGetHitlDetails = (queryClient: Quer
   dagRunId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   responseReceived?: boolean;
   state?: string[];
   subjectSearch?: string;
@@ -1353,7 +1353,7 @@ export const prefetchUseGridServiceGetDagStructure = 
(queryClient: QueryClient,
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
 }) => queryClient.prefetchQuery({ queryKey: 
Common.UseGridServiceGetDagStructureKeyFn({ dagId, limit, offset, orderBy, 
runAfterGte, runAfterLte }), queryFn: () => GridService.getDagStructure({ 
dagId, limit, offset, orderBy, runAfterGte, runAfterLte }) });
@@ -1374,7 +1374,7 @@ export const prefetchUseGridServiceGetGridRuns = 
(queryClient: QueryClient, { da
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
 }) => queryClient.prefetchQuery({ queryKey: 
Common.UseGridServiceGetGridRunsKeyFn({ dagId, limit, offset, orderBy, 
runAfterGte, runAfterLte }), queryFn: () => GridService.getGridRuns({ dagId, 
limit, offset, orderBy, runAfterGte, runAfterLte }) });
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
index f598ce9f423..e26bc9fc64c 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -24,7 +24,7 @@ export const useAssetServiceGetAssets = <TData = 
Common.AssetServiceGetAssetsDef
   namePattern?: string;
   offset?: number;
   onlyActive?: boolean;
-  orderBy?: string;
+  orderBy?: string[];
   uriPattern?: string;
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseAssetServiceGetAssetsKeyFn({ dagIds, limit, namePattern, offset, 
onlyActive, orderBy, uriPattern }, queryKey), queryFn: () => 
AssetService.getAssets({ dagIds, limit, namePattern, offset, onlyActive, 
orderBy, uriPattern }) as TData, ...options });
 /**
@@ -42,7 +42,7 @@ export const useAssetServiceGetAssetAliases = <TData = 
Common.AssetServiceGetAss
   limit?: number;
   namePattern?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseAssetServiceGetAssetAliasesKeyFn({ limit, namePattern, offset, 
orderBy }, queryKey), queryFn: () => AssetService.getAssetAliases({ limit, 
namePattern, offset, orderBy }) as TData, ...options });
 /**
 * Get Asset Alias
@@ -76,7 +76,7 @@ export const useAssetServiceGetAssetEvents = <TData = 
Common.AssetServiceGetAsse
   assetId?: number;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   sourceDagId?: string;
   sourceMapIndex?: number;
   sourceRunId?: string;
@@ -160,7 +160,7 @@ export const useBackfillServiceListBackfills = <TData = 
Common.BackfillServiceLi
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 }, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseBackfillServiceListBackfillsKeyFn({ dagId, limit, offset, orderBy }, 
queryKey), queryFn: () => BackfillService.listBackfills({ dagId, limit, offset, 
orderBy }) as TData, ...options });
 /**
 * Get Backfill
@@ -188,7 +188,7 @@ export const useBackfillServiceListBackfillsUi = <TData = 
Common.BackfillService
   dagId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseBackfillServiceListBackfillsUiKeyFn({ active, dagId, limit, offset, 
orderBy }, queryKey), queryFn: () => BackfillService.listBackfillsUi({ active, 
dagId, limit, offset, orderBy }) as TData, ...options });
 /**
 * Get Connection
@@ -216,7 +216,7 @@ export const useConnectionServiceGetConnections = <TData = 
Common.ConnectionServ
   connectionIdPattern?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseConnectionServiceGetConnectionsKeyFn({ connectionIdPattern, limit, 
offset, orderBy }, queryKey), queryFn: () => ConnectionService.getConnections({ 
connectionIdPattern, limit, offset, orderBy }) as TData, ...options });
 /**
 * Hook Meta Data
@@ -284,7 +284,7 @@ export const useDagRunServiceGetDagRuns = <TData = 
Common.DagRunServiceGetDagRun
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
   runIdPattern?: string;
@@ -415,7 +415,7 @@ export const useDagWarningServiceListDagWarnings = <TData = 
Common.DagWarningSer
   dagId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   warningType?: DagWarningType;
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseDagWarningServiceListDagWarningsKeyFn({ dagId, limit, offset, 
orderBy, warningType }, queryKey), queryFn: () => 
DagWarningService.listDagWarnings({ dagId, limit, offset, orderBy, warningType 
}) as TData, ...options });
 /**
@@ -455,7 +455,7 @@ export const useDagServiceGetDags = <TData = 
Common.DagServiceGetDagsDefaultResp
   lastDagRunState?: DagRunState;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owners?: string[];
   paused?: boolean;
   tags?: string[];
@@ -497,7 +497,7 @@ export const useDagServiceGetDagDetails = <TData = 
Common.DagServiceGetDagDetail
 export const useDagServiceGetDagTags = <TData = 
Common.DagServiceGetDagTagsDefaultResponse, TError = unknown, TQueryKey extends 
Array<unknown> = unknown[]>({ limit, offset, orderBy, tagNamePattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   tagNamePattern?: string;
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseDagServiceGetDagTagsKeyFn({ limit, offset, orderBy, tagNamePattern }, 
queryKey), queryFn: () => DagService.getDagTags({ limit, offset, orderBy, 
tagNamePattern }) as TData, ...options });
 /**
@@ -531,7 +531,7 @@ export const useDagServiceGetDagsUi = <TData = 
Common.DagServiceGetDagsUiDefault
   lastDagRunState?: DagRunState;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owners?: string[];
   paused?: boolean;
   tags?: string[];
@@ -589,7 +589,7 @@ export const useEventLogServiceGetEventLogs = <TData = 
Common.EventLogServiceGet
   limit?: number;
   mapIndex?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owner?: string;
   runId?: string;
   taskId?: string;
@@ -686,7 +686,7 @@ export const useTaskInstanceServiceGetMappedTaskInstances = 
<TData = Common.Task
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   pool?: string[];
   queue?: string[];
   runAfterGte?: string;
@@ -829,7 +829,7 @@ export const useTaskInstanceServiceGetTaskInstances = 
<TData = Common.TaskInstan
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   pool?: string[];
   queue?: string[];
   runAfterGte?: string;
@@ -948,7 +948,7 @@ export const useImportErrorServiceGetImportError = <TData = 
Common.ImportErrorSe
 export const useImportErrorServiceGetImportErrors = <TData = 
Common.ImportErrorServiceGetImportErrorsDefaultResponse, TError = unknown, 
TQueryKey extends Array<unknown> = unknown[]>({ limit, offset, orderBy }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseImportErrorServiceGetImportErrorsKeyFn({ limit, offset, orderBy }, 
queryKey), queryFn: () => ImportErrorService.getImportErrors({ limit, offset, 
orderBy }) as TData, ...options });
 /**
 * Get Jobs
@@ -979,7 +979,7 @@ export const useJobServiceGetJobs = <TData = 
Common.JobServiceGetJobsDefaultResp
   jobType?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   startDateGte?: string;
   startDateLte?: string;
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseJobServiceGetJobsKeyFn({ endDateGte, endDateLte, executorClass, 
hostname, isAlive, jobState, jobType, limit, offset, orderBy, startDateGte, 
startDateLte }, queryKey), queryFn: () => JobService.getJobs({ endDateGte, 
endDateLte, executorClass, hostname, isAlive, jobState, jobType, limit, offset, 
orderBy, startDateGte, startDateLte }) as TDat [...]
@@ -1026,7 +1026,7 @@ export const usePoolServiceGetPool = <TData = 
Common.PoolServiceGetPoolDefaultRe
 export const usePoolServiceGetPools = <TData = 
Common.PoolServiceGetPoolsDefaultResponse, TError = unknown, TQueryKey extends 
Array<unknown> = unknown[]>({ limit, offset, orderBy, poolNamePattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   poolNamePattern?: string;
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UsePoolServiceGetPoolsKeyFn({ limit, offset, orderBy, poolNamePattern }, 
queryKey), queryFn: () => PoolService.getPools({ limit, offset, orderBy, 
poolNamePattern }) as TData, ...options });
 /**
@@ -1141,7 +1141,7 @@ export const useVariableServiceGetVariable = <TData = 
Common.VariableServiceGetV
 export const useVariableServiceGetVariables = <TData = 
Common.VariableServiceGetVariablesDefaultResponse, TError = unknown, TQueryKey 
extends Array<unknown> = unknown[]>({ limit, offset, orderBy, 
variableKeyPattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   variableKeyPattern?: string;
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseVariableServiceGetVariablesKeyFn({ limit, offset, orderBy, 
variableKeyPattern }, queryKey), queryFn: () => VariableService.getVariables({ 
limit, offset, orderBy, variableKeyPattern }) as TData, ...options });
 /**
@@ -1179,7 +1179,7 @@ export const useDagVersionServiceGetDagVersions = <TData 
= Common.DagVersionServ
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   versionNumber?: number;
 }, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseDagVersionServiceGetDagVersionsKeyFn({ bundleName, bundleVersion, 
dagId, limit, offset, orderBy, versionNumber }, queryKey), queryFn: () => 
DagVersionService.getDagVersions({ bundleName, bundleVersion, dagId, limit, 
offset, orderBy, versionNumber }) as TData, ...options });
 /**
@@ -1237,7 +1237,7 @@ export const useHumanInTheLoopServiceGetHitlDetails = 
<TData = Common.HumanInThe
   dagRunId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   responseReceived?: boolean;
   state?: string[];
   subjectSearch?: string;
@@ -1353,7 +1353,7 @@ export const useGridServiceGetDagStructure = <TData = 
Common.GridServiceGetDagSt
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
 }, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseGridServiceGetDagStructureKeyFn({ dagId, limit, offset, orderBy, 
runAfterGte, runAfterLte }, queryKey), queryFn: () => 
GridService.getDagStructure({ dagId, limit, offset, orderBy, runAfterGte, 
runAfterLte }) as TData, ...options });
@@ -1374,7 +1374,7 @@ export const useGridServiceGetGridRuns = <TData = 
Common.GridServiceGetGridRunsD
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
 }, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseGridServiceGetGridRunsKeyFn({ dagId, limit, offset, orderBy, 
runAfterGte, runAfterLte }, queryKey), queryFn: () => GridService.getGridRuns({ 
dagId, limit, offset, orderBy, runAfterGte, runAfterLte }) as TData, ...options 
});
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
index 2d4773f6fa8..e1e5d6cd1ea 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -24,7 +24,7 @@ export const useAssetServiceGetAssetsSuspense = <TData = 
Common.AssetServiceGetA
   namePattern?: string;
   offset?: number;
   onlyActive?: boolean;
-  orderBy?: string;
+  orderBy?: string[];
   uriPattern?: string;
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseAssetServiceGetAssetsKeyFn({ dagIds, limit, namePattern, offset, 
onlyActive, orderBy, uriPattern }, queryKey), queryFn: () => 
AssetService.getAssets({ dagIds, limit, namePattern, offset, onlyActive, 
orderBy, uriPattern }) as TData, ...options });
 /**
@@ -42,7 +42,7 @@ export const useAssetServiceGetAssetAliasesSuspense = <TData 
= Common.AssetServi
   limit?: number;
   namePattern?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseAssetServiceGetAssetAliasesKeyFn({ limit, namePattern, offset, 
orderBy }, queryKey), queryFn: () => AssetService.getAssetAliases({ limit, 
namePattern, offset, orderBy }) as TData, ...options });
 /**
 * Get Asset Alias
@@ -76,7 +76,7 @@ export const useAssetServiceGetAssetEventsSuspense = <TData = 
Common.AssetServic
   assetId?: number;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   sourceDagId?: string;
   sourceMapIndex?: number;
   sourceRunId?: string;
@@ -160,7 +160,7 @@ export const useBackfillServiceListBackfillsSuspense = 
<TData = Common.BackfillS
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 }, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseBackfillServiceListBackfillsKeyFn({ dagId, limit, offset, orderBy }, 
queryKey), queryFn: () => BackfillService.listBackfills({ dagId, limit, offset, 
orderBy }) as TData, ...options });
 /**
 * Get Backfill
@@ -188,7 +188,7 @@ export const useBackfillServiceListBackfillsUiSuspense = 
<TData = Common.Backfil
   dagId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseBackfillServiceListBackfillsUiKeyFn({ active, dagId, limit, offset, 
orderBy }, queryKey), queryFn: () => BackfillService.listBackfillsUi({ active, 
dagId, limit, offset, orderBy }) as TData, ...options });
 /**
 * Get Connection
@@ -216,7 +216,7 @@ export const useConnectionServiceGetConnectionsSuspense = 
<TData = Common.Connec
   connectionIdPattern?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseConnectionServiceGetConnectionsKeyFn({ connectionIdPattern, limit, 
offset, orderBy }, queryKey), queryFn: () => ConnectionService.getConnections({ 
connectionIdPattern, limit, offset, orderBy }) as TData, ...options });
 /**
 * Hook Meta Data
@@ -284,7 +284,7 @@ export const useDagRunServiceGetDagRunsSuspense = <TData = 
Common.DagRunServiceG
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
   runIdPattern?: string;
@@ -415,7 +415,7 @@ export const useDagWarningServiceListDagWarningsSuspense = 
<TData = Common.DagWa
   dagId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   warningType?: DagWarningType;
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseDagWarningServiceListDagWarningsKeyFn({ dagId, limit, offset, 
orderBy, warningType }, queryKey), queryFn: () => 
DagWarningService.listDagWarnings({ dagId, limit, offset, orderBy, warningType 
}) as TData, ...options });
 /**
@@ -455,7 +455,7 @@ export const useDagServiceGetDagsSuspense = <TData = 
Common.DagServiceGetDagsDef
   lastDagRunState?: DagRunState;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owners?: string[];
   paused?: boolean;
   tags?: string[];
@@ -497,7 +497,7 @@ export const useDagServiceGetDagDetailsSuspense = <TData = 
Common.DagServiceGetD
 export const useDagServiceGetDagTagsSuspense = <TData = 
Common.DagServiceGetDagTagsDefaultResponse, TError = unknown, TQueryKey extends 
Array<unknown> = unknown[]>({ limit, offset, orderBy, tagNamePattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   tagNamePattern?: string;
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseDagServiceGetDagTagsKeyFn({ limit, offset, orderBy, tagNamePattern }, 
queryKey), queryFn: () => DagService.getDagTags({ limit, offset, orderBy, 
tagNamePattern }) as TData, ...options });
 /**
@@ -531,7 +531,7 @@ export const useDagServiceGetDagsUiSuspense = <TData = 
Common.DagServiceGetDagsU
   lastDagRunState?: DagRunState;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owners?: string[];
   paused?: boolean;
   tags?: string[];
@@ -589,7 +589,7 @@ export const useEventLogServiceGetEventLogsSuspense = 
<TData = Common.EventLogSe
   limit?: number;
   mapIndex?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   owner?: string;
   runId?: string;
   taskId?: string;
@@ -686,7 +686,7 @@ export const 
useTaskInstanceServiceGetMappedTaskInstancesSuspense = <TData = Com
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   pool?: string[];
   queue?: string[];
   runAfterGte?: string;
@@ -829,7 +829,7 @@ export const useTaskInstanceServiceGetTaskInstancesSuspense 
= <TData = Common.Ta
   logicalDateGte?: string;
   logicalDateLte?: string;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   pool?: string[];
   queue?: string[];
   runAfterGte?: string;
@@ -948,7 +948,7 @@ export const useImportErrorServiceGetImportErrorSuspense = 
<TData = Common.Impor
 export const useImportErrorServiceGetImportErrorsSuspense = <TData = 
Common.ImportErrorServiceGetImportErrorsDefaultResponse, TError = unknown, 
TQueryKey extends Array<unknown> = unknown[]>({ limit, offset, orderBy }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseImportErrorServiceGetImportErrorsKeyFn({ limit, offset, orderBy }, 
queryKey), queryFn: () => ImportErrorService.getImportErrors({ limit, offset, 
orderBy }) as TData, ...options });
 /**
 * Get Jobs
@@ -979,7 +979,7 @@ export const useJobServiceGetJobsSuspense = <TData = 
Common.JobServiceGetJobsDef
   jobType?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   startDateGte?: string;
   startDateLte?: string;
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseJobServiceGetJobsKeyFn({ endDateGte, endDateLte, executorClass, 
hostname, isAlive, jobState, jobType, limit, offset, orderBy, startDateGte, 
startDateLte }, queryKey), queryFn: () => JobService.getJobs({ endDateGte, 
endDateLte, executorClass, hostname, isAlive, jobState, jobType, limit, offset, 
orderBy, startDateGte, startDateLte }) [...]
@@ -1026,7 +1026,7 @@ export const usePoolServiceGetPoolSuspense = <TData = 
Common.PoolServiceGetPoolD
 export const usePoolServiceGetPoolsSuspense = <TData = 
Common.PoolServiceGetPoolsDefaultResponse, TError = unknown, TQueryKey extends 
Array<unknown> = unknown[]>({ limit, offset, orderBy, poolNamePattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   poolNamePattern?: string;
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UsePoolServiceGetPoolsKeyFn({ limit, offset, orderBy, poolNamePattern }, 
queryKey), queryFn: () => PoolService.getPools({ limit, offset, orderBy, 
poolNamePattern }) as TData, ...options });
 /**
@@ -1141,7 +1141,7 @@ export const useVariableServiceGetVariableSuspense = 
<TData = Common.VariableSer
 export const useVariableServiceGetVariablesSuspense = <TData = 
Common.VariableServiceGetVariablesDefaultResponse, TError = unknown, TQueryKey 
extends Array<unknown> = unknown[]>({ limit, offset, orderBy, 
variableKeyPattern }: {
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   variableKeyPattern?: string;
 } = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseVariableServiceGetVariablesKeyFn({ limit, offset, orderBy, 
variableKeyPattern }, queryKey), queryFn: () => VariableService.getVariables({ 
limit, offset, orderBy, variableKeyPattern }) as TData, ...options });
 /**
@@ -1179,7 +1179,7 @@ export const useDagVersionServiceGetDagVersionsSuspense = 
<TData = Common.DagVer
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   versionNumber?: number;
 }, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseDagVersionServiceGetDagVersionsKeyFn({ bundleName, bundleVersion, 
dagId, limit, offset, orderBy, versionNumber }, queryKey), queryFn: () => 
DagVersionService.getDagVersions({ bundleName, bundleVersion, dagId, limit, 
offset, orderBy, versionNumber }) as TData, ...options });
 /**
@@ -1237,7 +1237,7 @@ export const 
useHumanInTheLoopServiceGetHitlDetailsSuspense = <TData = Common.Hu
   dagRunId?: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   responseReceived?: boolean;
   state?: string[];
   subjectSearch?: string;
@@ -1353,7 +1353,7 @@ export const useGridServiceGetDagStructureSuspense = 
<TData = Common.GridService
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
 }, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseGridServiceGetDagStructureKeyFn({ dagId, limit, offset, orderBy, 
runAfterGte, runAfterLte }, queryKey), queryFn: () => 
GridService.getDagStructure({ dagId, limit, offset, orderBy, runAfterGte, 
runAfterLte }) as TData, ...options });
@@ -1374,7 +1374,7 @@ export const useGridServiceGetGridRunsSuspense = <TData = 
Common.GridServiceGetG
   dagId: string;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: string[];
   runAfterGte?: string;
   runAfterLte?: string;
 }, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseGridServiceGetGridRunsKeyFn({ dagId, limit, offset, orderBy, 
runAfterGte, runAfterLte }, queryKey), queryFn: () => GridService.getGridRuns({ 
dagId, limit, offset, orderBy, runAfterGte, runAfterLte }) as TData, ...options 
});
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 c5a8664463c..07e8141047d 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
@@ -1941,7 +1941,7 @@ export type GetAssetsData = {
     namePattern?: string | null;
     offset?: number;
     onlyActive?: boolean;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     /**
      * SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). 
Regular expressions are **not** supported.
      */
@@ -1957,7 +1957,7 @@ export type GetAssetAliasesData = {
      */
     namePattern?: string | null;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
 };
 
 export type GetAssetAliasesResponse = AssetAliasCollectionResponse;
@@ -1972,7 +1972,7 @@ export type GetAssetEventsData = {
     assetId?: number | null;
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     sourceDagId?: string | null;
     sourceMapIndex?: number | null;
     sourceRunId?: string | null;
@@ -2057,7 +2057,7 @@ export type ListBackfillsData = {
     dagId: string;
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
 };
 
 export type ListBackfillsResponse = BackfillCollectionResponse;
@@ -2103,7 +2103,7 @@ export type ListBackfillsUiData = {
     dagId?: string | null;
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
 };
 
 export type ListBackfillsUiResponse = BackfillCollectionResponse;
@@ -2135,7 +2135,7 @@ export type GetConnectionsData = {
     connectionIdPattern?: string | null;
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
 };
 
 export type GetConnectionsResponse = ConnectionCollectionResponse;
@@ -2208,7 +2208,7 @@ export type GetDagRunsData = {
     logicalDateGte?: string | null;
     logicalDateLte?: string | null;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     runAfterGte?: string | null;
     runAfterLte?: string | null;
     /**
@@ -2295,7 +2295,7 @@ export type ListDagWarningsData = {
     dagId?: string | null;
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     warningType?: DagWarningType | null;
 };
 
@@ -2320,7 +2320,7 @@ export type GetDagsData = {
     lastDagRunState?: DagRunState | null;
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     owners?: Array<(string)>;
     paused?: boolean | null;
     tags?: Array<(string)>;
@@ -2388,7 +2388,7 @@ export type UnfavoriteDagResponse = void;
 export type GetDagTagsData = {
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     /**
      * SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). 
Regular expressions are **not** supported.
      */
@@ -2413,7 +2413,7 @@ export type GetDagsUiData = {
     lastDagRunState?: DagRunState | null;
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     owners?: Array<(string)>;
     paused?: boolean | null;
     tags?: Array<(string)>;
@@ -2444,7 +2444,7 @@ export type GetEventLogsData = {
     limit?: number;
     mapIndex?: number | null;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     owner?: string | null;
     runId?: string | null;
     taskId?: string | null;
@@ -2502,7 +2502,7 @@ export type GetMappedTaskInstancesData = {
     logicalDateGte?: string | null;
     logicalDateLte?: string | null;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     pool?: Array<(string)>;
     queue?: Array<(string)>;
     runAfterGte?: string | null;
@@ -2586,7 +2586,7 @@ export type GetTaskInstancesData = {
     logicalDateGte?: string | null;
     logicalDateLte?: string | null;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     pool?: Array<(string)>;
     queue?: Array<(string)>;
     runAfterGte?: string | null;
@@ -2703,7 +2703,7 @@ export type GetImportErrorResponse = ImportErrorResponse;
 export type GetImportErrorsData = {
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
 };
 
 export type GetImportErrorsResponse = ImportErrorCollectionResponse;
@@ -2718,7 +2718,7 @@ export type GetJobsData = {
     jobType?: string | null;
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     startDateGte?: string | null;
     startDateLte?: string | null;
 };
@@ -2757,7 +2757,7 @@ export type PatchPoolResponse = PoolResponse;
 export type GetPoolsData = {
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     /**
      * SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). 
Regular expressions are **not** supported.
      */
@@ -2865,7 +2865,7 @@ export type PatchVariableResponse = VariableResponse;
 export type GetVariablesData = {
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     /**
      * SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). 
Regular expressions are **not** supported.
      */
@@ -2905,7 +2905,7 @@ export type GetDagVersionsData = {
     dagId: string;
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     versionNumber?: number;
 };
 
@@ -2959,7 +2959,7 @@ export type GetHitlDetailsData = {
     dagRunId?: string;
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     responseReceived?: boolean | null;
     state?: Array<(string)>;
     /**
@@ -3019,7 +3019,7 @@ export type GetDagStructureData = {
     dagId: string;
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     runAfterGte?: string | null;
     runAfterLte?: string | null;
 };
@@ -3030,7 +3030,7 @@ export type GetGridRunsData = {
     dagId: string;
     limit?: number;
     offset?: number;
-    orderBy?: string;
+    orderBy?: Array<(string)>;
     runAfterGte?: string | null;
     runAfterLte?: string | null;
 };
diff --git a/airflow-core/src/airflow/ui/src/components/DagVersionSelect.tsx 
b/airflow-core/src/airflow/ui/src/components/DagVersionSelect.tsx
index 9ea751d2ed2..5e386a7c484 100644
--- a/airflow-core/src/airflow/ui/src/components/DagVersionSelect.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DagVersionSelect.tsx
@@ -36,7 +36,7 @@ type VersionSelected = {
 export const DagVersionSelect = ({ showLabel = true }: { readonly showLabel?: 
boolean }) => {
   const { t: translate } = useTranslation("components");
   const { dagId = "" } = useParams();
-  const { data, isLoading } = useDagVersionServiceGetDagVersions({ dagId, 
orderBy: "-version_number" });
+  const { data, isLoading } = useDagVersionServiceGetDagVersions({ dagId, 
orderBy: ["-version_number"] });
   const [searchParams, setSearchParams] = useSearchParams();
   const selectedVersionNumber = useSelectedVersion();
   const selectedVersion = data?.dag_versions.find((dv) => dv.version_number 
=== selectedVersionNumber);
diff --git a/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx 
b/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx
index 516a03ba9ff..51a1442fcc9 100644
--- a/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Asset/AssetLayout.tsx
@@ -41,7 +41,7 @@ export const AssetLayout = () => {
   const { setTableURLState, tableURLState } = useTableURLState();
   const { pagination, sorting } = tableURLState;
   const [sort] = sorting;
-  const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : "-timestamp";
+  const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : 
["-timestamp"];
 
   const { data: asset, isLoading } = useAssetServiceGetAsset(
     { assetId: assetId === undefined ? 0 : parseInt(assetId, 10) },
diff --git a/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx 
b/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx
index b524972fc0a..047b537bc95 100644
--- a/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/AssetsList/AssetsList.tsx
@@ -103,7 +103,7 @@ export const AssetsList = () => {
   const { setTableURLState, tableURLState } = useTableURLState();
   const { pagination, sorting } = tableURLState;
   const [sort] = sorting;
-  const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : undefined;
+  const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : undefined;
 
   const { data, error, isLoading } = useAssetServiceGetAssets({
     limit: pagination.pageSize,
diff --git a/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx 
b/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx
index 5a33e52975c..4ff052ec3a9 100644
--- a/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Connections/Connections.tsx
@@ -135,7 +135,7 @@ export const Connections = () => {
   useConnectionTypeMeta(); // Pre-fetch connection type metadata
   const { pagination, sorting } = tableURLState;
   const [sort] = sorting;
-  const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : "connection_id";
+  const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : 
["connection_id"];
   const { data, error, isFetching, isLoading } = 
useConnectionServiceGetConnections({
     connectionIdPattern: connectionIdPattern ?? undefined,
     limit: pagination.pageSize,
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
index 665b47b5da4..0a0c13c4ef2 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
@@ -50,7 +50,7 @@ export const Overview = () => {
   const { data: failedTasks, isLoading } = 
useTaskInstanceServiceGetTaskInstances({
     dagId: dagId ?? "",
     dagRunId: "~",
-    orderBy: "-run_after",
+    orderBy: ["-run_after"],
     runAfterGte: startDate,
     runAfterLte: endDate,
     state: ["failed"],
@@ -67,7 +67,7 @@ export const Overview = () => {
   const { data: gridRuns, isLoading: isLoadingRuns } = useGridRuns({ limit });
   const { data: assetEventsData, isLoading: isLoadingAssetEvents } = 
useAssetServiceGetAssetEvents({
     limit,
-    orderBy: assetSortBy,
+    orderBy: [assetSortBy],
     sourceDagId: dagId,
     timestampGte: startDate,
     timestampLte: endDate,
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Tasks/TaskCard.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Tasks/TaskCard.tsx
index 09f7de15f6e..83f1635cbc3 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Tasks/TaskCard.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Tasks/TaskCard.tsx
@@ -44,7 +44,7 @@ export const TaskCard = ({ dagId, task }: Props) => {
       dagId,
       dagRunId: "~",
       limit: 14,
-      orderBy: "-run_after",
+      orderBy: ["-run_after"],
       taskId: task.task_id ?? "",
     },
     undefined,
diff --git a/airflow-core/src/airflow/ui/src/pages/DagRuns.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagRuns.tsx
index 1ea3f965a07..8215488a753 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagRuns.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagRuns.tsx
@@ -162,7 +162,7 @@ export const DagRuns = () => {
   const { setTableURLState, tableURLState } = useTableURLState();
   const { pagination, sorting } = tableURLState;
   const [sort] = sorting;
-  const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : "-run_after";
+  const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : 
["-run_after"];
 
   const { pageIndex, pageSize } = pagination;
   const filteredState = searchParams.get(STATE_PARAM);
diff --git 
a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx
index 5a72213671f..bb84ed99fcf 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsFilters/DagsFilters.tsx
@@ -84,7 +84,7 @@ export const DagsFilters = () => {
 
   const { data, fetchNextPage, fetchPreviousPage } = useDagTagsInfinite({
     limit: 10,
-    orderBy: "name",
+    orderBy: ["name"],
     tagNamePattern: pattern,
   });
 
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
index b9f3136f0b0..7874008f6ad 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
@@ -250,7 +250,7 @@ export const DagsList = () => {
     lastDagRunState,
     limit: pagination.pageSize,
     offset: pagination.pageIndex * pagination.pageSize,
-    orderBy,
+    orderBy: [orderBy],
     owners,
     paused,
     tags: selectedTags,
diff --git 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx
 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx
index 24230eaba00..2ccae75e919 100644
--- 
a/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx
+++ 
b/airflow-core/src/airflow/ui/src/pages/Dashboard/HistoricalMetrics/HistoricalMetrics.tsx
@@ -63,7 +63,7 @@ export const HistoricalMetrics = () => {
 
   const { data: assetEventsData, isLoading: isLoadingAssetEvents } = 
useAssetServiceGetAssetEvents({
     limit: 6,
-    orderBy: assetSortBy,
+    orderBy: [assetSortBy],
     timestampGte: startDate,
     timestampLte: endDate,
   });
diff --git a/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx 
b/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx
index f71e57b6419..d804ee30686 100644
--- a/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Events/Events.tsx
@@ -149,7 +149,7 @@ export const Events = () => {
   const [sort] = sorting;
   const { onClose, onOpen, open } = useDisclosure();
 
-  const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : "-when";
+  const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : ["-when"];
 
   const { data, error, isFetching, isLoading } = 
useEventLogServiceGetEventLogs(
     {
diff --git a/airflow-core/src/airflow/ui/src/pages/Pools/Pools.tsx 
b/airflow-core/src/airflow/ui/src/pages/Pools/Pools.tsx
index ff1ca88c9f4..8ab1cbfb493 100644
--- a/airflow-core/src/airflow/ui/src/pages/Pools/Pools.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Pools/Pools.tsx
@@ -59,7 +59,7 @@ export const Pools = () => {
   const { setTableURLState, tableURLState } = useTableURLState();
   const { pagination, sorting } = tableURLState;
   const [sort] = sorting;
-  const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : "name";
+  const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : ["name"];
 
   const { data, error, isLoading } = usePoolServiceGetPools({
     limit: pagination.pageSize,
diff --git a/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx 
b/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx
index 79b39d21b9f..492f15feb8b 100644
--- a/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx
@@ -57,7 +57,7 @@ export const Overview = () => {
       dagId,
       dagRunId: "~",
       limit: 14,
-      orderBy: "-run_after",
+      orderBy: ["-run_after"],
       taskDisplayNamePattern: groupId ?? undefined,
       taskId: Boolean(groupId) ? undefined : taskId,
     },
diff --git 
a/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx 
b/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx
index 13d084a5c74..6285253571e 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstances/TaskInstances.tsx
@@ -188,7 +188,7 @@ export const TaskInstances = () => {
   const { setTableURLState, tableURLState } = useTableURLState();
   const { pagination, sorting } = tableURLState;
   const [sort] = sorting;
-  const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id}` : "-start_date";
+  const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id}`] : 
["-start_date"];
 
   const filteredState = searchParams.getAll(STATE_PARAM);
   const startDate = searchParams.get(START_DATE_PARAM);
diff --git a/airflow-core/src/airflow/ui/src/pages/Variables/Variables.tsx 
b/airflow-core/src/airflow/ui/src/pages/Variables/Variables.tsx
index 052da890b36..3e4ccc2927a 100644
--- a/airflow-core/src/airflow/ui/src/pages/Variables/Variables.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Variables/Variables.tsx
@@ -124,7 +124,7 @@ export const Variables = () => {
   const [selectedVariables, setSelectedVariables] = useState<Record<string, 
string | undefined>>({});
   const { pagination, sorting } = tableURLState;
   const [sort] = sorting;
-  const orderBy = sort ? `${sort.desc ? "-" : ""}${sort.id === "value" ? 
"_val" : sort.id}` : "-key";
+  const orderBy = sort ? [`${sort.desc ? "-" : ""}${sort.id === "value" ? 
"_val" : sort.id}`] : ["-key"];
 
   const { data, error, isFetching, isLoading } = 
useVariableServiceGetVariables({
     limit: pagination.pageSize,
diff --git a/airflow-core/src/airflow/ui/src/queries/useDagTagsInfinite.ts 
b/airflow-core/src/airflow/ui/src/queries/useDagTagsInfinite.ts
index 40a779734f5..d049928c0ed 100644
--- a/airflow-core/src/airflow/ui/src/queries/useDagTagsInfinite.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useDagTagsInfinite.ts
@@ -29,7 +29,7 @@ export const useDagTagsInfinite = <TError = unknown>(
     tagNamePattern,
   }: {
     limit?: number;
-    orderBy?: string;
+    orderBy?: Array<string>;
     tagNamePattern?: string;
   } = {},
   queryKey?: Array<unknown>,
diff --git a/airflow-core/src/airflow/ui/src/queries/useDags.tsx 
b/airflow-core/src/airflow/ui/src/queries/useDags.tsx
index 6cacce11e9c..349c12f97b5 100644
--- a/airflow-core/src/airflow/ui/src/queries/useDags.tsx
+++ b/airflow-core/src/airflow/ui/src/queries/useDags.tsx
@@ -47,7 +47,7 @@ export const useDags = ({
   lastDagRunState?: DagRunState;
   limit?: number;
   offset?: number;
-  orderBy?: string;
+  orderBy?: Array<string>;
   owners?: Array<string>;
   paused?: boolean;
   tags?: Array<string>;
diff --git a/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts 
b/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts
index 096630852d4..42807a9a7bc 100644
--- a/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useGridRuns.ts
@@ -30,7 +30,7 @@ export const useGridRuns = ({ limit }: { limit: number }) => {
     {
       dagId,
       limit,
-      orderBy: "-run_after",
+      orderBy: ["-run_after"],
     },
     undefined,
     {
diff --git a/airflow-core/src/airflow/ui/src/queries/useGridStructure.ts 
b/airflow-core/src/airflow/ui/src/queries/useGridStructure.ts
index eaeaede6050..17db10fffd5 100644
--- a/airflow-core/src/airflow/ui/src/queries/useGridStructure.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useGridStructure.ts
@@ -36,7 +36,7 @@ export const useGridStructure = ({
     {
       dagId,
       limit,
-      orderBy: "-run_after",
+      orderBy: ["-run_after"],
     },
     undefined,
     {
diff --git a/airflow-core/tests/unit/api_fastapi/common/db/test_dags.py 
b/airflow-core/tests/unit/api_fastapi/common/db/test_dags.py
index 051ffa965cc..41065fbd4fc 100644
--- a/airflow-core/tests/unit/api_fastapi/common/db/test_dags.py
+++ b/airflow-core/tests/unit/api_fastapi/common/db/test_dags.py
@@ -114,7 +114,7 @@ class TestGenerateDagWithLatestRunQuery:
         dag_model, _ = dag_with_queued_run
         query = generate_dag_with_latest_run_query(
             max_run_filters=[],
-            order_by=SortParam(allowed_attrs=["dag_id"], 
model=DagModel).set_value("dag_id"),
+            order_by=SortParam(allowed_attrs=["dag_id"], 
model=DagModel).set_value(["dag_id"]),
         )
 
         # Also fetch joined DagRun's state and start_date
@@ -134,7 +134,9 @@ class TestGenerateDagWithLatestRunQuery:
 
         query = generate_dag_with_latest_run_query(
             max_run_filters=[],
-            order_by=SortParam(allowed_attrs=["last_run_state"], 
model=DagModel).set_value("last_run_state"),
+            order_by=SortParam(allowed_attrs=["last_run_state"], 
model=DagModel).set_value(
+                ["last_run_state"]
+            ),
         )
         extended_query = query.add_columns(DagRun.state, DagRun.start_date)
         result = session.execute(extended_query).fetchall()
@@ -159,7 +161,7 @@ class TestGenerateDagWithLatestRunQuery:
         query = generate_dag_with_latest_run_query(
             max_run_filters=[],
             order_by=SortParam(allowed_attrs=["last_run_start_date"], 
model=DagModel).set_value(
-                "last_run_start_date"
+                ["last_run_start_date"]
             ),
         )
         extended_query = query.add_columns(DagRun.state, DagRun.start_date)
@@ -207,7 +209,9 @@ class TestGenerateDagWithLatestRunQuery:
         session.commit()
         query = generate_dag_with_latest_run_query(
             max_run_filters=[],
-            order_by=SortParam(allowed_attrs=["last_run_state"], 
model=DagModel).set_value("last_run_state"),
+            order_by=SortParam(allowed_attrs=["last_run_state"], 
model=DagModel).set_value(
+                ["last_run_state"]
+            ),
         )
         extended_query = query.add_columns(DagRun.state, DagRun.start_date)
         result = session.execute(extended_query).fetchall()
@@ -231,7 +235,9 @@ class TestGenerateDagWithLatestRunQuery:
         running_dag_model, _ = dag_with_running_run
         query = generate_dag_with_latest_run_query(
             max_run_filters=[],
-            order_by=SortParam(allowed_attrs=["last_run_state"], 
model=DagModel).set_value("last_run_state"),
+            order_by=SortParam(allowed_attrs=["last_run_state"], 
model=DagModel).set_value(
+                ["last_run_state"]
+            ),
         )
         extended_query = query.add_columns(DagRun.state, DagRun.start_date)
 
diff --git a/airflow-core/tests/unit/api_fastapi/common/test_parameters.py 
b/airflow-core/tests/unit/api_fastapi/common/test_parameters.py
new file mode 100644
index 00000000000..cd77ded8ace
--- /dev/null
+++ b/airflow-core/tests/unit/api_fastapi/common/test_parameters.py
@@ -0,0 +1,40 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+import re
+
+import pytest
+from fastapi import HTTPException
+
+from airflow.api_fastapi.common.parameters import SortParam
+
+
+class TestSortParam:
+    def test_sort_param_max_number_of_filers(self):
+        param = SortParam([], None, None)
+        n_filters = param.MAX_SORT_PARAMS + 1
+        param.value = [f"filter_{i}" for i in range(n_filters)]
+
+        with pytest.raises(
+            HTTPException,
+            match=re.escape(
+                f"400: Ordering with more than {param.MAX_SORT_PARAMS} 
parameters is not allowed. Provided: {param.value}"
+            ),
+        ):
+            param.to_orm(None)
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py
index 84dbf60c225..60302878dba 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dags.py
@@ -248,6 +248,11 @@ class TestGetDags(TestDagEndpoint):
                 3,
                 [DAG3_ID, DAG1_ID, DAG2_ID],
             ),
+            (
+                {"order_by": ["next_dagrun", "-dag_display_name"], 
"exclude_stale": False},
+                3,
+                [DAG3_ID, DAG2_ID, DAG1_ID],
+            ),
             # Search
             ({"dag_id_pattern": "1"}, 1, [DAG1_ID]),
             ({"dag_display_name_pattern": "test_dag2"}, 1, [DAG2_ID]),

Reply via email to