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):