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

bbovenzi pushed a commit to branch v3-2-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-2-test by this push:
     new 4538443867a [v3-2-test] Support ordering XCom entries in the REST API 
and UI (#65418) (#65600)
4538443867a is described below

commit 4538443867a99ee9e3ea0139213db60b213ec04a
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Apr 21 13:05:24 2026 -0400

    [v3-2-test] Support ordering XCom entries in the REST API and UI (#65418) 
(#65600)
    
    * Support ordering XCom entries in the REST API and UI
    
    * Fix CI error in XComs e2e spec
    
    * Replace networkidle wait in XComs sort test
    (cherry picked from commit 1fb2d0e36487a4fd8daefa7b6a33f0010622a160)
    
    Co-authored-by: Yuseok Jo <[email protected]>
---
 .../src/airflow/api_fastapi/common/parameters.py   | 13 ++++++++---
 .../core_api/openapi/v2-rest-api-generated.yaml    | 20 +++++++++++++++++
 .../api_fastapi/core_api/routes/public/xcom.py     | 15 ++++++++++---
 .../src/airflow/ui/openapi-gen/queries/common.ts   |  5 +++--
 .../ui/openapi-gen/queries/ensureQueryData.ts      |  6 +++--
 .../src/airflow/ui/openapi-gen/queries/prefetch.ts |  6 +++--
 .../src/airflow/ui/openapi-gen/queries/queries.ts  |  6 +++--
 .../src/airflow/ui/openapi-gen/queries/suspense.ts |  6 +++--
 .../ui/openapi-gen/requests/services.gen.ts        |  4 +++-
 .../airflow/ui/openapi-gen/requests/types.gen.ts   |  4 ++++
 .../src/airflow/ui/src/pages/XCom/XCom.tsx         | 14 +++++-------
 .../src/airflow/ui/tests/e2e/pages/XComsPage.ts    | 26 ++++++++++++++++++++++
 .../src/airflow/ui/tests/e2e/specs/xcoms.spec.ts   |  8 +++++++
 13 files changed, 108 insertions(+), 25 deletions(-)

diff --git a/airflow-core/src/airflow/api_fastapi/common/parameters.py 
b/airflow-core/src/airflow/api_fastapi/common/parameters.py
index 0206c9d0ac6..28180b4d81f 100644
--- a/airflow-core/src/airflow/api_fastapi/common/parameters.py
+++ b/airflow-core/src/airflow/api_fastapi/common/parameters.py
@@ -18,7 +18,7 @@
 from __future__ import annotations
 
 from abc import ABC, abstractmethod
-from collections.abc import Callable, Iterable
+from collections.abc import Callable, Iterable, Sequence
 from datetime import datetime
 from enum import Enum
 from typing import (
@@ -364,14 +364,21 @@ class SortParam(BaseParam[list[str]]):
     def depends(cls, *args: Any, **kwargs: Any) -> Self:
         raise NotImplementedError("Use dynamic_depends, depends not 
implemented.")
 
-    def dynamic_depends(self, default: str | None = None) -> Callable:
+    def dynamic_depends(self, default: str | Sequence[str] | None = None) -> 
Callable:
         to_replace_attrs = list(self.to_replace.keys()) if self.to_replace 
else []
 
         all_attrs = self.allowed_attrs + to_replace_attrs
 
+        if default is None:
+            default_list = [self.get_primary_key_string()]
+        elif isinstance(default, str):
+            default_list = [default]
+        else:
+            default_list = list(default)
+
         def inner(
             order_by: list[str] = Query(
-                default=[default] if default is not None else 
[self.get_primary_key_string()],
+                default=default_list,
                 description=f"Attributes to order by, multi criteria sort is 
supported. Prefix with `-` for descending order. "
                 f"Supported attributes: `{', '.join(all_attrs) if all_attrs 
else self.get_primary_key_string()}`",
             ),
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 d8579e98449..b69b98e6a12 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
@@ -5348,6 +5348,26 @@ paths:
             format: date-time
           - type: 'null'
           title: Run After Lt
+      - name: order_by
+        in: query
+        required: false
+        schema:
+          type: array
+          items:
+            type: string
+          description: 'Attributes to order by, multi criteria sort is 
supported.
+            Prefix with `-` for descending order. Supported attributes: `key, 
dag_id,
+            run_id, task_id, map_index, timestamp, run_after`'
+          default:
+          - dag_id
+          - task_id
+          - run_id
+          - map_index
+          - key
+          title: Order By
+        description: 'Attributes to order by, multi criteria sort is 
supported. Prefix
+          with `-` for descending order. Supported attributes: `key, dag_id, 
run_id,
+          task_id, map_index, timestamp, run_after`'
       responses:
         '200':
           description: Successful Response
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/xcom.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/xcom.py
index 7bf64592aa6..7f5b49ca2e2 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/xcom.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/xcom.py
@@ -36,6 +36,7 @@ from airflow.api_fastapi.common.parameters import (
     QueryXComRunIdPatternSearch,
     QueryXComTaskIdPatternSearch,
     RangeFilter,
+    SortParam,
     datetime_range_filter_factory,
     filter_param_factory,
 )
@@ -162,6 +163,16 @@ def get_xcom_entries(
     ],
     logical_date_range: Annotated[RangeFilter, 
Depends(datetime_range_filter_factory("logical_date", DR))],
     run_after_range: Annotated[RangeFilter, 
Depends(datetime_range_filter_factory("run_after", DR))],
+    order_by: Annotated[
+        SortParam,
+        Depends(
+            SortParam(
+                ["key", "dag_id", "run_id", "task_id", "map_index", 
"timestamp"],
+                XComModel,
+                to_replace={"run_after": DR.run_after},
+            ).dynamic_depends(default=("dag_id", "task_id", "run_id", 
"map_index", "key"))
+        ),
+    ],
     xcom_key: Annotated[str | None, Query()] = None,
     map_index: Annotated[int | None, Query(ge=-1)] = None,
 ) -> XComCollectionResponse:
@@ -200,13 +211,11 @@ def get_xcom_entries(
             logical_date_range,
             run_after_range,
         ],
+        order_by=order_by,
         offset=offset,
         limit=limit,
         session=session,
     )
-    query = query.order_by(
-        XComModel.dag_id, XComModel.task_id, XComModel.run_id, 
XComModel.map_index, XComModel.key
-    )
     return XComCollectionResponse(xcom_entries=session.scalars(query), 
total_entries=total_entries)
 
 
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 6c87c143157..25b8985c4e9 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -696,7 +696,7 @@ export const UseXcomServiceGetXcomEntryKeyFn = ({ dagId, 
dagRunId, deserialize,
 export type XcomServiceGetXcomEntriesDefaultResponse = 
Awaited<ReturnType<typeof XcomService.getXcomEntries>>;
 export type XcomServiceGetXcomEntriesQueryResult<TData = 
XcomServiceGetXcomEntriesDefaultResponse, TError = unknown> = 
UseQueryResult<TData, TError>;
 export const useXcomServiceGetXcomEntriesKey = "XcomServiceGetXcomEntries";
-export const UseXcomServiceGetXcomEntriesKeyFn = ({ dagDisplayNamePattern, 
dagId, dagRunId, limit, logicalDateGt, logicalDateGte, logicalDateLt, 
logicalDateLte, mapIndex, mapIndexFilter, offset, runAfterGt, runAfterGte, 
runAfterLt, runAfterLte, runIdPattern, taskId, taskIdPattern, xcomKey, 
xcomKeyPattern }: {
+export const UseXcomServiceGetXcomEntriesKeyFn = ({ dagDisplayNamePattern, 
dagId, dagRunId, limit, logicalDateGt, logicalDateGte, logicalDateLt, 
logicalDateLte, mapIndex, mapIndexFilter, offset, orderBy, runAfterGt, 
runAfterGte, runAfterLt, runAfterLte, runIdPattern, taskId, taskIdPattern, 
xcomKey, xcomKeyPattern }: {
   dagDisplayNamePattern?: string;
   dagId: string;
   dagRunId: string;
@@ -708,6 +708,7 @@ export const UseXcomServiceGetXcomEntriesKeyFn = ({ 
dagDisplayNamePattern, dagId
   mapIndex?: number;
   mapIndexFilter?: number;
   offset?: number;
+  orderBy?: string[];
   runAfterGt?: string;
   runAfterGte?: string;
   runAfterLt?: string;
@@ -717,7 +718,7 @@ export const UseXcomServiceGetXcomEntriesKeyFn = ({ 
dagDisplayNamePattern, dagId
   taskIdPattern?: string;
   xcomKey?: string;
   xcomKeyPattern?: string;
-}, queryKey?: Array<unknown>) => [useXcomServiceGetXcomEntriesKey, 
...(queryKey ?? [{ dagDisplayNamePattern, dagId, dagRunId, limit, 
logicalDateGt, logicalDateGte, logicalDateLt, logicalDateLte, mapIndex, 
mapIndexFilter, offset, runAfterGt, runAfterGte, runAfterLt, runAfterLte, 
runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }])];
+}, queryKey?: Array<unknown>) => [useXcomServiceGetXcomEntriesKey, 
...(queryKey ?? [{ dagDisplayNamePattern, dagId, dagRunId, limit, 
logicalDateGt, logicalDateGte, logicalDateLt, logicalDateLte, mapIndex, 
mapIndexFilter, offset, orderBy, runAfterGt, runAfterGte, runAfterLt, 
runAfterLte, runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }])];
 export type TaskServiceGetTasksDefaultResponse = Awaited<ReturnType<typeof 
TaskService.getTasks>>;
 export type TaskServiceGetTasksQueryResult<TData = 
TaskServiceGetTasksDefaultResponse, TError = unknown> = UseQueryResult<TData, 
TError>;
 export const useTaskServiceGetTasksKey = "TaskServiceGetTasks";
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 23e47e6e21f..6fbc3416bb9 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -1352,10 +1352,11 @@ export const ensureUseXcomServiceGetXcomEntryData = 
(queryClient: QueryClient, {
 * @param data.runAfterGt
 * @param data.runAfterLte
 * @param data.runAfterLt
+* @param data.orderBy Attributes to order by, multi criteria sort is 
supported. Prefix with `-` for descending order. Supported attributes: `key, 
dag_id, run_id, task_id, map_index, timestamp, run_after`
 * @returns XComCollectionResponse Successful Response
 * @throws ApiError
 */
-export const ensureUseXcomServiceGetXcomEntriesData = (queryClient: 
QueryClient, { dagDisplayNamePattern, dagId, dagRunId, limit, logicalDateGt, 
logicalDateGte, logicalDateLt, logicalDateLte, mapIndex, mapIndexFilter, 
offset, runAfterGt, runAfterGte, runAfterLt, runAfterLte, runIdPattern, taskId, 
taskIdPattern, xcomKey, xcomKeyPattern }: {
+export const ensureUseXcomServiceGetXcomEntriesData = (queryClient: 
QueryClient, { dagDisplayNamePattern, dagId, dagRunId, limit, logicalDateGt, 
logicalDateGte, logicalDateLt, logicalDateLte, mapIndex, mapIndexFilter, 
offset, orderBy, runAfterGt, runAfterGte, runAfterLt, runAfterLte, 
runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }: {
   dagDisplayNamePattern?: string;
   dagId: string;
   dagRunId: string;
@@ -1367,6 +1368,7 @@ export const ensureUseXcomServiceGetXcomEntriesData = 
(queryClient: QueryClient,
   mapIndex?: number;
   mapIndexFilter?: number;
   offset?: number;
+  orderBy?: string[];
   runAfterGt?: string;
   runAfterGte?: string;
   runAfterLt?: string;
@@ -1376,7 +1378,7 @@ export const ensureUseXcomServiceGetXcomEntriesData = 
(queryClient: QueryClient,
   taskIdPattern?: string;
   xcomKey?: string;
   xcomKeyPattern?: string;
-}) => queryClient.ensureQueryData({ queryKey: 
Common.UseXcomServiceGetXcomEntriesKeyFn({ dagDisplayNamePattern, dagId, 
dagRunId, limit, logicalDateGt, logicalDateGte, logicalDateLt, logicalDateLte, 
mapIndex, mapIndexFilter, offset, runAfterGt, runAfterGte, runAfterLt, 
runAfterLte, runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }), 
queryFn: () => XcomService.getXcomEntries({ dagDisplayNamePattern, dagId, 
dagRunId, limit, logicalDateGt, logicalDateGte, logicalDateLt, logicalD [...]
+}) => queryClient.ensureQueryData({ queryKey: 
Common.UseXcomServiceGetXcomEntriesKeyFn({ dagDisplayNamePattern, dagId, 
dagRunId, limit, logicalDateGt, logicalDateGte, logicalDateLt, logicalDateLte, 
mapIndex, mapIndexFilter, offset, orderBy, runAfterGt, runAfterGte, runAfterLt, 
runAfterLte, runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }), 
queryFn: () => XcomService.getXcomEntries({ dagDisplayNamePattern, dagId, 
dagRunId, limit, logicalDateGt, logicalDateGte, logicalDateLt, [...]
 /**
 * Get Tasks
 * Get tasks for DAG.
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 f9d107a5b1d..840cd9508d5 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -1352,10 +1352,11 @@ export const prefetchUseXcomServiceGetXcomEntry = 
(queryClient: QueryClient, { d
 * @param data.runAfterGt
 * @param data.runAfterLte
 * @param data.runAfterLt
+* @param data.orderBy Attributes to order by, multi criteria sort is 
supported. Prefix with `-` for descending order. Supported attributes: `key, 
dag_id, run_id, task_id, map_index, timestamp, run_after`
 * @returns XComCollectionResponse Successful Response
 * @throws ApiError
 */
-export const prefetchUseXcomServiceGetXcomEntries = (queryClient: QueryClient, 
{ dagDisplayNamePattern, dagId, dagRunId, limit, logicalDateGt, logicalDateGte, 
logicalDateLt, logicalDateLte, mapIndex, mapIndexFilter, offset, runAfterGt, 
runAfterGte, runAfterLt, runAfterLte, runIdPattern, taskId, taskIdPattern, 
xcomKey, xcomKeyPattern }: {
+export const prefetchUseXcomServiceGetXcomEntries = (queryClient: QueryClient, 
{ dagDisplayNamePattern, dagId, dagRunId, limit, logicalDateGt, logicalDateGte, 
logicalDateLt, logicalDateLte, mapIndex, mapIndexFilter, offset, orderBy, 
runAfterGt, runAfterGte, runAfterLt, runAfterLte, runIdPattern, taskId, 
taskIdPattern, xcomKey, xcomKeyPattern }: {
   dagDisplayNamePattern?: string;
   dagId: string;
   dagRunId: string;
@@ -1367,6 +1368,7 @@ export const prefetchUseXcomServiceGetXcomEntries = 
(queryClient: QueryClient, {
   mapIndex?: number;
   mapIndexFilter?: number;
   offset?: number;
+  orderBy?: string[];
   runAfterGt?: string;
   runAfterGte?: string;
   runAfterLt?: string;
@@ -1376,7 +1378,7 @@ export const prefetchUseXcomServiceGetXcomEntries = 
(queryClient: QueryClient, {
   taskIdPattern?: string;
   xcomKey?: string;
   xcomKeyPattern?: string;
-}) => queryClient.prefetchQuery({ queryKey: 
Common.UseXcomServiceGetXcomEntriesKeyFn({ dagDisplayNamePattern, dagId, 
dagRunId, limit, logicalDateGt, logicalDateGte, logicalDateLt, logicalDateLte, 
mapIndex, mapIndexFilter, offset, runAfterGt, runAfterGte, runAfterLt, 
runAfterLte, runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }), 
queryFn: () => XcomService.getXcomEntries({ dagDisplayNamePattern, dagId, 
dagRunId, limit, logicalDateGt, logicalDateGte, logicalDateLt, logicalDat [...]
+}) => queryClient.prefetchQuery({ queryKey: 
Common.UseXcomServiceGetXcomEntriesKeyFn({ dagDisplayNamePattern, dagId, 
dagRunId, limit, logicalDateGt, logicalDateGte, logicalDateLt, logicalDateLte, 
mapIndex, mapIndexFilter, offset, orderBy, runAfterGt, runAfterGte, runAfterLt, 
runAfterLte, runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }), 
queryFn: () => XcomService.getXcomEntries({ dagDisplayNamePattern, dagId, 
dagRunId, limit, logicalDateGt, logicalDateGte, logicalDateLt, l [...]
 /**
 * Get Tasks
 * Get tasks for DAG.
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 c9dd84d1458..af30805d4e1 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -1352,10 +1352,11 @@ export const useXcomServiceGetXcomEntry = <TData = 
Common.XcomServiceGetXcomEntr
 * @param data.runAfterGt
 * @param data.runAfterLte
 * @param data.runAfterLt
+* @param data.orderBy Attributes to order by, multi criteria sort is 
supported. Prefix with `-` for descending order. Supported attributes: `key, 
dag_id, run_id, task_id, map_index, timestamp, run_after`
 * @returns XComCollectionResponse Successful Response
 * @throws ApiError
 */
-export const useXcomServiceGetXcomEntries = <TData = 
Common.XcomServiceGetXcomEntriesDefaultResponse, TError = unknown, TQueryKey 
extends Array<unknown> = unknown[]>({ dagDisplayNamePattern, dagId, dagRunId, 
limit, logicalDateGt, logicalDateGte, logicalDateLt, logicalDateLte, mapIndex, 
mapIndexFilter, offset, runAfterGt, runAfterGte, runAfterLt, runAfterLte, 
runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }: {
+export const useXcomServiceGetXcomEntries = <TData = 
Common.XcomServiceGetXcomEntriesDefaultResponse, TError = unknown, TQueryKey 
extends Array<unknown> = unknown[]>({ dagDisplayNamePattern, dagId, dagRunId, 
limit, logicalDateGt, logicalDateGte, logicalDateLt, logicalDateLte, mapIndex, 
mapIndexFilter, offset, orderBy, runAfterGt, runAfterGte, runAfterLt, 
runAfterLte, runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }: {
   dagDisplayNamePattern?: string;
   dagId: string;
   dagRunId: string;
@@ -1367,6 +1368,7 @@ export const useXcomServiceGetXcomEntries = <TData = 
Common.XcomServiceGetXcomEn
   mapIndex?: number;
   mapIndexFilter?: number;
   offset?: number;
+  orderBy?: string[];
   runAfterGt?: string;
   runAfterGte?: string;
   runAfterLt?: string;
@@ -1376,7 +1378,7 @@ export const useXcomServiceGetXcomEntries = <TData = 
Common.XcomServiceGetXcomEn
   taskIdPattern?: string;
   xcomKey?: string;
   xcomKeyPattern?: string;
-}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseXcomServiceGetXcomEntriesKeyFn({ dagDisplayNamePattern, dagId, 
dagRunId, limit, logicalDateGt, logicalDateGte, logicalDateLt, logicalDateLte, 
mapIndex, mapIndexFilter, offset, runAfterGt, runAfterGte, runAfterLt, 
runAfterLte, runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }, 
queryKey), queryFn: () => XcomService.getXcomEntries({ d [...]
+}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseXcomServiceGetXcomEntriesKeyFn({ dagDisplayNamePattern, dagId, 
dagRunId, limit, logicalDateGt, logicalDateGte, logicalDateLt, logicalDateLte, 
mapIndex, mapIndexFilter, offset, orderBy, runAfterGt, runAfterGte, runAfterLt, 
runAfterLte, runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }, 
queryKey), queryFn: () => XcomService.getXcomEn [...]
 /**
 * Get Tasks
 * Get tasks for DAG.
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 4727215089c..e1453e19e42 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -1352,10 +1352,11 @@ export const useXcomServiceGetXcomEntrySuspense = 
<TData = Common.XcomServiceGet
 * @param data.runAfterGt
 * @param data.runAfterLte
 * @param data.runAfterLt
+* @param data.orderBy Attributes to order by, multi criteria sort is 
supported. Prefix with `-` for descending order. Supported attributes: `key, 
dag_id, run_id, task_id, map_index, timestamp, run_after`
 * @returns XComCollectionResponse Successful Response
 * @throws ApiError
 */
-export const useXcomServiceGetXcomEntriesSuspense = <TData = 
Common.XcomServiceGetXcomEntriesDefaultResponse, TError = unknown, TQueryKey 
extends Array<unknown> = unknown[]>({ dagDisplayNamePattern, dagId, dagRunId, 
limit, logicalDateGt, logicalDateGte, logicalDateLt, logicalDateLte, mapIndex, 
mapIndexFilter, offset, runAfterGt, runAfterGte, runAfterLt, runAfterLte, 
runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }: {
+export const useXcomServiceGetXcomEntriesSuspense = <TData = 
Common.XcomServiceGetXcomEntriesDefaultResponse, TError = unknown, TQueryKey 
extends Array<unknown> = unknown[]>({ dagDisplayNamePattern, dagId, dagRunId, 
limit, logicalDateGt, logicalDateGte, logicalDateLt, logicalDateLte, mapIndex, 
mapIndexFilter, offset, orderBy, runAfterGt, runAfterGte, runAfterLt, 
runAfterLte, runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }: {
   dagDisplayNamePattern?: string;
   dagId: string;
   dagRunId: string;
@@ -1367,6 +1368,7 @@ export const useXcomServiceGetXcomEntriesSuspense = 
<TData = Common.XcomServiceG
   mapIndex?: number;
   mapIndexFilter?: number;
   offset?: number;
+  orderBy?: string[];
   runAfterGt?: string;
   runAfterGte?: string;
   runAfterLt?: string;
@@ -1376,7 +1378,7 @@ export const useXcomServiceGetXcomEntriesSuspense = 
<TData = Common.XcomServiceG
   taskIdPattern?: string;
   xcomKey?: string;
   xcomKeyPattern?: string;
-}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseXcomServiceGetXcomEntriesKeyFn({ dagDisplayNamePattern, dagId, 
dagRunId, limit, logicalDateGt, logicalDateGte, logicalDateLt, logicalDateLte, 
mapIndex, mapIndexFilter, offset, runAfterGt, runAfterGte, runAfterLt, 
runAfterLte, runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }, 
queryKey), queryFn: () => XcomService.getXcomEnt [...]
+}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseXcomServiceGetXcomEntriesKeyFn({ dagDisplayNamePattern, dagId, 
dagRunId, limit, logicalDateGt, logicalDateGte, logicalDateLt, logicalDateLte, 
mapIndex, mapIndexFilter, offset, orderBy, runAfterGt, runAfterGte, runAfterLt, 
runAfterLte, runIdPattern, taskId, taskIdPattern, xcomKey, xcomKeyPattern }, 
queryKey), queryFn: () => XcomService.g [...]
 /**
 * Get Tasks
 * Get tasks for DAG.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts 
b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
index b27ad6008e4..fff137401c7 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
@@ -3384,6 +3384,7 @@ export class XcomService {
      * @param data.runAfterGt
      * @param data.runAfterLte
      * @param data.runAfterLt
+     * @param data.orderBy Attributes to order by, multi criteria sort is 
supported. Prefix with `-` for descending order. Supported attributes: `key, 
dag_id, run_id, task_id, map_index, timestamp, run_after`
      * @returns XComCollectionResponse Successful Response
      * @throws ApiError
      */
@@ -3413,7 +3414,8 @@ export class XcomService {
                 run_after_gte: data.runAfterGte,
                 run_after_gt: data.runAfterGt,
                 run_after_lte: data.runAfterLte,
-                run_after_lt: data.runAfterLt
+                run_after_lt: data.runAfterLt,
+                order_by: data.orderBy
             },
             errors: {
                 400: 'Bad Request',
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 438780ce825..c582d575b96 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
@@ -3469,6 +3469,10 @@ export type GetXcomEntriesData = {
     mapIndex?: number | null;
     mapIndexFilter?: number | null;
     offset?: number;
+    /**
+     * Attributes to order by, multi criteria sort is supported. Prefix with 
`-` for descending order. Supported attributes: `key, dag_id, run_id, task_id, 
map_index, timestamp, run_after`
+     */
+    orderBy?: Array<(string)>;
     runAfterGt?: string | null;
     runAfterGte?: string | null;
     runAfterLt?: string | null;
diff --git a/airflow-core/src/airflow/ui/src/pages/XCom/XCom.tsx 
b/airflow-core/src/airflow/ui/src/pages/XCom/XCom.tsx
index 66895c76cc6..4bf1a5a8229 100644
--- a/airflow-core/src/airflow/ui/src/pages/XCom/XCom.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/XCom/XCom.tsx
@@ -54,7 +54,6 @@ type ColumnsProps = {
 const getColumns = ({ open, translate }: ColumnsProps): 
Array<ColumnDef<XComResponse>> => [
   {
     accessorKey: "key",
-    enableSorting: false,
     header: translate("xcom.columns.key"),
   },
   {
@@ -64,7 +63,6 @@ const getColumns = ({ open, translate }: ColumnsProps): 
Array<ColumnDef<XComResp
         <RouterLink 
to={`/dags/${original.dag_id}`}>{original.dag_display_name}</RouterLink>
       </Link>
     ),
-    enableSorting: false,
     header: translate("xcom.columns.dag"),
   },
   {
@@ -76,7 +74,6 @@ const getColumns = ({ open, translate }: ColumnsProps): 
Array<ColumnDef<XComResp
         </RouterLink>
       </Link>
     ),
-    enableSorting: false,
     header: translate("common:dagRunId"),
   },
   {
@@ -88,7 +85,6 @@ const getColumns = ({ open, translate }: ColumnsProps): 
Array<ColumnDef<XComResp
         </RouterLink>
       </Link>
     ),
-    enableSorting: false,
     header: translate("common:dagRun.runAfter"),
   },
   {
@@ -107,18 +103,15 @@ const getColumns = ({ open, translate }: ColumnsProps): 
Array<ColumnDef<XComResp
         </RouterLink>
       </Link>
     ),
-    enableSorting: false,
     header: translate("common:task_one"),
   },
   {
     accessorKey: "map_index",
-    enableSorting: false,
     header: translate("common:mapIndex"),
   },
   {
     accessorKey: "timestamp",
     cell: ({ row: { original } }) => <Time datetime={original.timestamp} />,
-    enableSorting: false,
     header: translate("dashboard:timestamp"),
   },
   {
@@ -152,7 +145,11 @@ export const XCom = () => {
   const { dagId = "~", mapIndex = "-1", runId = "~", taskId = "~" } = 
useParams();
   const { t: translate } = useTranslation(["browse", "common"]);
   const { setTableURLState, tableURLState } = useTableURLState();
-  const { pagination } = tableURLState;
+  const { pagination, sorting } = tableURLState;
+  const [sort] = sorting;
+  const orderBy = sort
+    ? [`${sort.desc ? "-" : ""}${sort.id === "task_display_name" ? "task_id" : 
sort.id}`]
+    : undefined;
   const [searchParams] = useSearchParams();
   const { onClose, onOpen, open } = useDisclosure();
 
@@ -182,6 +179,7 @@ export const XCom = () => {
           ? undefined
           : parseInt(mapIndex, 10),
     offset: pagination.pageIndex * pagination.pageSize,
+    orderBy,
     runAfterGte: runAfterGte ?? undefined,
     runAfterLte: runAfterLte ?? undefined,
     runIdPattern: filteredRunId ?? undefined,
diff --git a/airflow-core/src/airflow/ui/tests/e2e/pages/XComsPage.ts 
b/airflow-core/src/airflow/ui/tests/e2e/pages/XComsPage.ts
index 188a5d81fd9..8278da752b3 100644
--- a/airflow-core/src/airflow/ui/tests/e2e/pages/XComsPage.ts
+++ b/airflow-core/src/airflow/ui/tests/e2e/pages/XComsPage.ts
@@ -123,6 +123,32 @@ export class XComsPage extends BasePage {
     }
   }
 
+  public async verifySortByColumn(columnName: string): Promise<void> {
+    await this.navigate();
+
+    const columnHeader = this.xcomsTable.locator("thead th").filter({ hasText: 
columnName });
+
+    await expect(columnHeader).toBeVisible({ timeout: 10_000 });
+
+    const firstSortResponse = this.page.waitForResponse(
+      (response) => response.url().includes("xcomEntries") && response.ok(),
+      { timeout: 10_000 },
+    );
+
+    await columnHeader.click();
+    await firstSortResponse;
+    await expect(this.page).toHaveURL(/sorting/);
+
+    const secondSortResponse = this.page.waitForResponse(
+      (response) => response.url().includes("xcomEntries") && response.ok(),
+      { timeout: 10_000 },
+    );
+
+    await columnHeader.click();
+    await secondSortResponse;
+    await expect(this.page).toHaveURL(/sorting/);
+  }
+
   public async verifyXComDetailsDisplay(): Promise<void> {
     const firstRow = this.xcomsTable.locator("tbody tr").first();
 
diff --git a/airflow-core/src/airflow/ui/tests/e2e/specs/xcoms.spec.ts 
b/airflow-core/src/airflow/ui/tests/e2e/specs/xcoms.spec.ts
index 3911ab27c8a..bcc3c778167 100644
--- a/airflow-core/src/airflow/ui/tests/e2e/specs/xcoms.spec.ts
+++ b/airflow-core/src/airflow/ui/tests/e2e/specs/xcoms.spec.ts
@@ -96,4 +96,12 @@ test.describe("XComs Page", () => {
   test("verify filtering by DAG display name", async () => {
     await xcomsPage.verifyDagDisplayNameFiltering(testDagId);
   });
+
+  test("verify sorting by key column", async ({ xcomsPage }) => {
+    await xcomsPage.verifySortByColumn("Key");
+  });
+
+  test("verify sorting by timestamp column", async ({ xcomsPage }) => {
+    await xcomsPage.verifySortByColumn("Timestamp");
+  });
 });

Reply via email to