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

weilee 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 f08f5975e7d Return empty list for queued asset events instead of 404 
(#62934)
f08f5975e7d is described below

commit f08f5975e7d93844ff8f226fb32ed27b243255fa
Author: Guan-Ming (Wesley) Chiu <[email protected]>
AuthorDate: Fri Mar 6 11:48:44 2026 +0800

    Return empty list for queued asset events instead of 404 (#62934)
---
 .../core_api/openapi/v2-rest-api-generated.yaml    | 12 --------
 .../api_fastapi/core_api/routes/public/assets.py   | 10 -------
 .../ui/openapi-gen/requests/services.gen.ts        |  2 --
 .../airflow/ui/openapi-gen/requests/types.gen.ts   |  8 -----
 .../src/airflow/ui/src/pages/Dag/Header.tsx        |  1 -
 .../ui/src/pages/DagsList/AssetSchedule.tsx        | 34 +++++-----------------
 .../src/airflow/ui/src/pages/DagsList/DagCard.tsx  |  1 -
 .../src/airflow/ui/src/pages/DagsList/DagsList.tsx | 23 ++++++---------
 .../src/airflow/ui/src/pages/DagsList/Schedule.tsx |  3 --
 .../core_api/routes/public/test_assets.py          | 12 ++++----
 10 files changed, 22 insertions(+), 84 deletions(-)

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 92ca0df16b3..5ece802761c 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
@@ -571,12 +571,6 @@ paths:
               schema:
                 $ref: '#/components/schemas/HTTPExceptionResponse'
           description: Forbidden
-        '404':
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/HTTPExceptionResponse'
-          description: Not Found
         '422':
           description: Validation Error
           content:
@@ -726,12 +720,6 @@ paths:
               schema:
                 $ref: '#/components/schemas/HTTPExceptionResponse'
           description: Forbidden
-        '404':
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/HTTPExceptionResponse'
-          description: Not Found
         '422':
           description: Validation Error
           content:
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py
index 1384e532746..65e90a321b1 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py
@@ -429,7 +429,6 @@ def materialize_asset(
 
 @assets_router.get(
     "/assets/{asset_id}/queuedEvents",
-    responses=create_openapi_http_exception_doc([status.HTTP_404_NOT_FOUND]),
     dependencies=[Depends(requires_access_asset(method="GET"))],
 )
 def get_asset_queued_events(
@@ -447,12 +446,6 @@ def get_asset_queued_events(
     dag_asset_queued_events_select, total_entries = 
paginated_select(statement=query)
     adrqs = session.scalars(dag_asset_queued_events_select).all()
 
-    if not adrqs:
-        raise HTTPException(
-            status.HTTP_404_NOT_FOUND,
-            f"Queue event with asset_id: `{asset_id}` was not found",
-        )
-
     queued_events = [
         QueuedEventResponse(
             created_at=adrq.created_at,
@@ -536,7 +529,6 @@ def get_asset(
 
 @assets_router.get(
     "/dags/{dag_id}/assets/queuedEvents",
-    responses=create_openapi_http_exception_doc([status.HTTP_404_NOT_FOUND]),
     dependencies=[Depends(requires_access_asset(method="GET")), 
Depends(requires_access_dag(method="GET"))],
 )
 def get_dag_asset_queued_events(
@@ -553,8 +545,6 @@ def get_dag_asset_queued_events(
 
     dag_asset_queued_events_select, total_entries = 
paginated_select(statement=query)
     adrqs = session.scalars(dag_asset_queued_events_select).all()
-    if not adrqs:
-        raise HTTPException(status.HTTP_404_NOT_FOUND, f"Queue event with 
dag_id: `{dag_id}` was not found")
 
     queued_events = [
         QueuedEventResponse(
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 62d70dca2c3..ec616b7e2bc 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
@@ -215,7 +215,6 @@ export class AssetService {
             errors: {
                 401: 'Unauthorized',
                 403: 'Forbidden',
-                404: 'Not Found',
                 422: 'Validation Error'
             }
         });
@@ -295,7 +294,6 @@ export class AssetService {
             errors: {
                 401: 'Unauthorized',
                 403: 'Forbidden',
-                404: 'Not Found',
                 422: 'Validation Error'
             }
         });
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 fe7062b70df..2a92af9ca3c 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
@@ -3854,10 +3854,6 @@ export type $OpenApiTs = {
                  * Forbidden
                  */
                 403: HTTPExceptionResponse;
-                /**
-                 * Not Found
-                 */
-                404: HTTPExceptionResponse;
                 /**
                  * Validation Error
                  */
@@ -3933,10 +3929,6 @@ export type $OpenApiTs = {
                  * Forbidden
                  */
                 403: HTTPExceptionResponse;
-                /**
-                 * Not Found
-                 */
-                404: HTTPExceptionResponse;
                 /**
                  * Validation Error
                  */
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Header.tsx 
b/airflow-core/src/airflow/ui/src/pages/Dag/Header.tsx
index 21ece580905..6af9129a316 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Header.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Header.tsx
@@ -65,7 +65,6 @@ export const Header = ({
           <Schedule
             assetExpression={dag.asset_expression}
             dagId={dag.dag_id}
-            latestRunAfter={latestRunInfo?.run_after}
             timetableDescription={dag.timetable_description}
             timetablePartitioned={dag.timetable_partitioned}
             timetableSummary={dag.timetable_summary}
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/AssetSchedule.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/AssetSchedule.tsx
index 20fe71dd041..78536cc1c1a 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/AssetSchedule.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/AssetSchedule.tsx
@@ -34,7 +34,6 @@ import { PartitionScheduleModal } from 
"./PartitionScheduleModal";
 type Props = {
   readonly assetExpression?: ExpressionType | null;
   readonly dagId: string;
-  readonly latestRunAfter?: string;
   readonly timetablePartitioned: boolean | null;
   readonly timetableSummary: string | null;
 };
@@ -60,31 +59,20 @@ const PartitionSchedule = ({ dagId, isLoading, pendingCount 
}: PartitionSchedule
   );
 };
 
-export const AssetSchedule = ({
-  assetExpression,
-  dagId,
-  latestRunAfter,
-  timetablePartitioned,
-  timetableSummary,
-}: Props) => {
+export const AssetSchedule = ({ assetExpression, dagId, timetablePartitioned, 
timetableSummary }: Props) => {
   const { t: translate } = useTranslation(["dags", "common"]);
 
   const { data: nextRun, isLoading: isNextRunLoading } = 
useAssetServiceNextRunAssets({ dagId });
-  const {
-    data: queuedEventsData,
-    error: queuedEventsError,
-    isLoading: isQueuedEventsLoading,
-  } = useAssetServiceGetDagAssetQueuedEvents({ dagId }, undefined, { enabled: 
!timetablePartitioned });
+  const { data: queuedEventsData, isLoading: isQueuedEventsLoading } = 
useAssetServiceGetDagAssetQueuedEvents(
+    { dagId },
+    undefined,
+    { enabled: !timetablePartitioned },
+  );
 
   const nextRunEvents = (nextRun?.events ?? []) as Array<NextRunEvent>;
-  const queuedEventsErrorStatus =
-    typeof queuedEventsError === "object" && queuedEventsError !== null && 
"status" in queuedEventsError
-      ? (queuedEventsError as { status?: number }).status
-      : undefined;
-  const hasQueuedEventsError = Boolean(queuedEventsError) && 
queuedEventsErrorStatus !== 404;
   const queuedAssetEvents = new Map<number, string>();
 
-  if (!timetablePartitioned && !hasQueuedEventsError) {
+  if (!timetablePartitioned) {
     for (const event of queuedEventsData?.queued_events ?? []) {
       // Keep a single event timestamp per asset, using the latest one when 
duplicates exist.
       const existingEventDate = queuedAssetEvents.get(event.asset_id);
@@ -100,14 +88,6 @@ export const AssetSchedule = ({
       return event.lastUpdate === null ? [] : [event];
     }
 
-    if (hasQueuedEventsError) {
-      if (event.lastUpdate === null) {
-        return [];
-      }
-
-      return latestRunAfter !== undefined && 
dayjs(event.lastUpdate).isAfter(latestRunAfter) ? [event] : [];
-    }
-
     const queuedAt = queuedAssetEvents.get(event.id);
 
     return queuedAt === undefined ? [] : [{ ...event, lastUpdate: 
event.lastUpdate ?? queuedAt }];
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx
index 872e3bba0f8..57b21d315e6 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagCard.tsx
@@ -76,7 +76,6 @@ export const DagCard = ({ dag }: Props) => {
           <Schedule
             assetExpression={dag.asset_expression}
             dagId={dag.dag_id}
-            latestRunAfter={latestRun?.run_after}
             timetableDescription={dag.timetable_description}
             timetablePartitioned={dag.timetable_partitioned}
             timetableSummary={dag.timetable_summary}
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 ae94c99ece2..616a924de40 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/DagsList.tsx
@@ -86,20 +86,15 @@ const createColumns = (
   },
   {
     accessorKey: "timetable_description",
-    cell: ({ row: { original } }) => {
-      const [latestRun] = original.latest_dag_runs;
-
-      return (
-        <Schedule
-          assetExpression={original.asset_expression}
-          dagId={original.dag_id}
-          latestRunAfter={latestRun?.run_after}
-          timetableDescription={original.timetable_description}
-          timetablePartitioned={original.timetable_partitioned}
-          timetableSummary={original.timetable_summary}
-        />
-      );
-    },
+    cell: ({ row: { original } }) => (
+      <Schedule
+        assetExpression={original.asset_expression}
+        dagId={original.dag_id}
+        timetableDescription={original.timetable_description}
+        timetablePartitioned={original.timetable_partitioned}
+        timetableSummary={original.timetable_summary}
+      />
+    ),
     enableSorting: false,
     header: () => translate("dagDetails.schedule"),
   },
diff --git a/airflow-core/src/airflow/ui/src/pages/DagsList/Schedule.tsx 
b/airflow-core/src/airflow/ui/src/pages/DagsList/Schedule.tsx
index f5999af3cf0..0f0268dd5f6 100644
--- a/airflow-core/src/airflow/ui/src/pages/DagsList/Schedule.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/DagsList/Schedule.tsx
@@ -28,7 +28,6 @@ import { AssetSchedule } from "./AssetSchedule";
 type Props = {
   readonly assetExpression: ExpressionType | null | undefined;
   readonly dagId: string;
-  readonly latestRunAfter?: string;
   readonly timetableDescription?: string | null;
   readonly timetablePartitioned: boolean | null;
   readonly timetableSummary: string | null;
@@ -37,7 +36,6 @@ type Props = {
 export const Schedule = ({
   assetExpression,
   dagId,
-  latestRunAfter,
   timetableDescription,
   timetablePartitioned,
   timetableSummary,
@@ -49,7 +47,6 @@ export const Schedule = ({
       <AssetSchedule
         assetExpression={assetExpression}
         dagId={dagId}
-        latestRunAfter={latestRunAfter}
         timetablePartitioned={timetablePartitioned}
         timetableSummary={timetableSummary}
       />
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py
index 3ef6188ce8d..4659bc8873f 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py
@@ -1196,15 +1196,15 @@ class 
TestGetDagAssetQueuedEvents(TestQueuedEventEndpoint):
         response = 
unauthorized_test_client.get("/dags/random/assets/queuedEvents")
         assert response.status_code == 403
 
-    def test_should_respond_404(self, test_client):
+    def test_should_respond_200_empty(self, test_client):
         dag_id = "not_exists"
 
         response = test_client.get(
             f"/dags/{dag_id}/assets/queuedEvents",
         )
 
-        assert response.status_code == 404
-        assert response.json()["detail"] == "Queue event with dag_id: 
`not_exists` was not found"
+        assert response.status_code == 200
+        assert response.json() == {"queued_events": [], "total_entries": 0}
 
 
 class TestDeleteDagDatasetQueuedEvents(TestQueuedEventEndpoint):
@@ -1477,10 +1477,10 @@ class TestGetAssetQueuedEvents(TestQueuedEventEndpoint):
         response = unauthorized_test_client.get("/assets/1/queuedEvents")
         assert response.status_code == 403
 
-    def test_should_respond_404(self, test_client):
+    def test_should_respond_200_empty(self, test_client):
         response = test_client.get("/assets/1/queuedEvents")
-        assert response.status_code == 404
-        assert response.json()["detail"] == "Queue event with asset_id: `1` 
was not found"
+        assert response.status_code == 200
+        assert response.json() == {"queued_events": [], "total_entries": 0}
 
 
 class TestDeleteAssetQueuedEvents(TestQueuedEventEndpoint):

Reply via email to