You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by ds...@apache.org on 2025/07/28 19:14:59 UTC

(airflow) branch main updated: Combine latest run info and grid latest run (#53680)

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

dstandish 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 17523392858 Combine latest run info and grid latest run (#53680)
17523392858 is described below

commit 17523392858980f2478733cbc89bf8f4a07b19f3
Author: Daniel Standish <15...@users.noreply.github.com>
AuthorDate: Mon Jul 28 12:14:53 2025 -0700

    Combine latest run info and grid latest run (#53680)
---
 .../api_fastapi/core_api/datamodels/ui/common.py   |  9 ---
 .../api_fastapi/core_api/datamodels/ui/dag_runs.py |  3 +-
 .../api_fastapi/core_api/openapi/_private_ui.yaml  | 90 +++-------------------
 .../airflow/api_fastapi/core_api/routes/ui/dags.py | 31 ++++----
 .../airflow/api_fastapi/core_api/routes/ui/grid.py | 47 -----------
 .../src/airflow/ui/openapi-gen/queries/common.ts   |  6 --
 .../ui/openapi-gen/queries/ensureQueryData.ts      | 15 +---
 .../src/airflow/ui/openapi-gen/queries/prefetch.ts | 15 +---
 .../src/airflow/ui/openapi-gen/queries/queries.ts  | 15 +---
 .../src/airflow/ui/openapi-gen/queries/suspense.ts | 15 +---
 .../airflow/ui/openapi-gen/requests/schemas.gen.ts | 38 ++-------
 .../ui/openapi-gen/requests/services.gen.ts        | 29 +------
 .../airflow/ui/openapi-gen/requests/types.gen.ts   | 46 +----------
 airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx  | 10 +--
 .../ui/src/queries/useRefreshOnNewDagRuns.ts       |  8 +-
 .../api_fastapi/core_api/routes/ui/test_dags.py    | 25 ++++++
 .../api_fastapi/core_api/routes/ui/test_grid.py    |  4 +-
 17 files changed, 79 insertions(+), 327 deletions(-)

diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/common.py b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/common.py
index 6ff8b83825f..2dee03e6510 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/common.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/common.py
@@ -93,12 +93,3 @@ class BaseGraphResponse(BaseModel, Generic[E, N]):
 
     edges: list[E]
     nodes: list[N]
-
-
-class LatestRunResponse(BaseModel):
-    """Base Node serializer for responses."""
-
-    id: int
-    dag_id: str
-    run_id: str
-    run_after: datetime
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/dag_runs.py b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/dag_runs.py
index 78944e9e786..d99761f7457 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/dag_runs.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/dag_runs.py
@@ -25,8 +25,9 @@ from airflow.utils.state import DagRunState
 class DAGRunLightResponse(BaseModel):
     """DAG Run serializer for responses."""
 
-    run_id: str
+    id: int
     dag_id: str
+    run_id: str
     logical_date: datetime | None
     run_after: datetime
     start_date: datetime | None
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 f8289bba786..742f4dafbc8 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
@@ -270,9 +270,9 @@ paths:
           content:
             application/json:
               schema:
-                type: array
-                items:
-                  $ref: '#/components/schemas/DAGRunLightResponse'
+                anyOf:
+                - $ref: '#/components/schemas/DAGRunLightResponse'
+                - type: 'null'
                 title: Response Get Latest Run Info
         '404':
           content:
@@ -777,55 +777,6 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
-  /ui/grid/latest_run/{dag_id}:
-    get:
-      tags:
-      - Grid
-      summary: Get Latest Run
-      description: 'Get information about the latest dag run by run_after.
-
-
-        This is used by the UI to figure out if it needs to rerun queries and resume
-        auto refresh.'
-      operationId: get_latest_run
-      security:
-      - OAuth2PasswordBearer: []
-      - HTTPBearer: []
-      parameters:
-      - name: dag_id
-        in: path
-        required: true
-        schema:
-          type: string
-          title: Dag Id
-      responses:
-        '200':
-          description: Successful Response
-          content:
-            application/json:
-              schema:
-                anyOf:
-                - $ref: '#/components/schemas/LatestRunResponse'
-                - type: 'null'
-                title: Response Get Latest Run
-        '400':
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/HTTPExceptionResponse'
-          description: Bad Request
-        '404':
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/HTTPExceptionResponse'
-          description: Not Found
-        '422':
-          description: Validation Error
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/HTTPValidationError'
   /ui/calendar/{dag_id}:
     get:
       tags:
@@ -1191,12 +1142,15 @@ components:
         the API server/Web UI can use this data to render connection form UI.'
     DAGRunLightResponse:
       properties:
-        run_id:
-          type: string
-          title: Run Id
+        id:
+          type: integer
+          title: Id
         dag_id:
           type: string
           title: Dag Id
+        run_id:
+          type: string
+          title: Run Id
         logical_date:
           anyOf:
           - type: string
@@ -1223,8 +1177,9 @@ components:
           $ref: '#/components/schemas/DagRunState'
       type: object
       required:
-      - run_id
+      - id
       - dag_id
+      - run_id
       - logical_date
       - run_after
       - start_date
@@ -1877,29 +1832,6 @@ components:
       - task_instance_states
       title: HistoricalMetricDataResponse
       description: Historical Metric Data serializer for responses.
-    LatestRunResponse:
-      properties:
-        id:
-          type: integer
-          title: Id
-        dag_id:
-          type: string
-          title: Dag Id
-        run_id:
-          type: string
-          title: Run Id
-        run_after:
-          type: string
-          format: date-time
-          title: Run After
-      type: object
-      required:
-      - id
-      - dag_id
-      - run_id
-      - run_after
-      title: LatestRunResponse
-      description: Base Node serializer for responses.
     LightGridTaskInstanceSummary:
       properties:
         task_id:
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dags.py b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dags.py
index 1ca67321a64..e48241dda02 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dags.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dags.py
@@ -22,7 +22,6 @@ from typing import Annotated
 
 from fastapi import Depends, status
 from sqlalchemy import and_, func, select
-from sqlalchemy.orm import load_only
 
 from airflow.api_fastapi.auth.managers.models.resource_details import DagAccessEntity
 from airflow.api_fastapi.common.db.common import (
@@ -202,29 +201,25 @@ def get_dags(
     responses=create_openapi_http_exception_doc([status.HTTP_404_NOT_FOUND]),
     dependencies=[Depends(requires_access_dag(method="GET", access_entity=DagAccessEntity.RUN))],
 )
-def get_latest_run_info(dag_id: str, session: SessionDep) -> list[DAGRunLightResponse]:
+def get_latest_run_info(dag_id: str, session: SessionDep) -> DAGRunLightResponse | None:
     """Get latest run."""
     if dag_id == "~":
         raise HTTPException(
             status.HTTP_400_BAD_REQUEST,
             "`~` was supplied as dag_id, but querying multiple dags is not supported.",
         )
-    query = (
-        select(DagRun)
+    return session.execute(
+        select(
+            DagRun.id,
+            DagRun.dag_id,
+            DagRun.run_id,
+            DagRun.end_date,
+            DagRun.logical_date,
+            DagRun.run_after,
+            DagRun.start_date,
+            DagRun.state,
+        )
         .where(DagRun.dag_id == dag_id)
         .order_by(DagRun.run_after.desc())
         .limit(1)
-        .options(
-            load_only(
-                DagRun.dag_id,
-                DagRun.run_id,
-                DagRun.end_date,
-                DagRun.logical_date,
-                DagRun.run_after,
-                DagRun.start_date,
-                DagRun.state,
-            )
-        )
-    )
-    dag_runs = session.scalars(query)
-    return dag_runs
+    ).one_or_none()
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 80e68bd890f..fd217f3fbdf 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
@@ -37,7 +37,6 @@ from airflow.api_fastapi.common.router import AirflowRouter
 from airflow.api_fastapi.core_api.datamodels.ui.common import (
     GridNodeResponse,
     GridRunsResponse,
-    LatestRunResponse,
 )
 from airflow.api_fastapi.core_api.datamodels.ui.grid import (
     GridTISummaries,
@@ -348,49 +347,3 @@ def get_grid_ti_summaries(
         "dag_id": dag_id,
         "task_instances": list(get_node_sumaries()),
     }
-
-
-@grid_router.get(
-    "/latest_run/{dag_id}",
-    responses=create_openapi_http_exception_doc(
-        [
-            status.HTTP_400_BAD_REQUEST,
-            status.HTTP_404_NOT_FOUND,
-        ]
-    ),
-    dependencies=[
-        Depends(
-            requires_access_dag(
-                method="GET",
-                access_entity=DagAccessEntity.TASK_INSTANCE,
-            )
-        ),
-        Depends(
-            requires_access_dag(
-                method="GET",
-                access_entity=DagAccessEntity.RUN,
-            )
-        ),
-    ],
-    response_model_exclude_none=True,
-)
-def get_latest_run(
-    dag_id: str,
-    session: SessionDep,
-) -> LatestRunResponse | None:
-    """
-    Get information about the latest dag run by run_after.
-
-    This is used by the UI to figure out if it needs to rerun queries and resume auto refresh.
-    """
-    return session.execute(
-        select(
-            DagRun.id,
-            DagRun.dag_id,
-            DagRun.run_id,
-            DagRun.run_after,
-        )
-        .where(DagRun.dag_id == dag_id)
-        .order_by(DagRun.run_after.desc())
-        .limit(1)
-    ).one_or_none()
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 f33564ac279..e4e52a6c871 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -745,12 +745,6 @@ export const UseGridServiceGetGridTiSummariesKeyFn = ({ dagId, runId }: {
   dagId: string;
   runId: string;
 }, queryKey?: Array<unknown>) => [useGridServiceGetGridTiSummariesKey, ...(queryKey ?? [{ dagId, runId }])];
-export type GridServiceGetLatestRunDefaultResponse = Awaited<ReturnType<typeof GridService.getLatestRun>>;
-export type GridServiceGetLatestRunQueryResult<TData = GridServiceGetLatestRunDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>;
-export const useGridServiceGetLatestRunKey = "GridServiceGetLatestRun";
-export const UseGridServiceGetLatestRunKeyFn = ({ dagId }: {
-  dagId: string;
-}, queryKey?: Array<unknown>) => [useGridServiceGetLatestRunKey, ...(queryKey ?? [{ dagId }])];
 export type CalendarServiceGetCalendarDefaultResponse = Awaited<ReturnType<typeof CalendarService.getCalendar>>;
 export type CalendarServiceGetCalendarQueryResult<TData = CalendarServiceGetCalendarDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>;
 export const useCalendarServiceGetCalendarKey = "CalendarServiceGetCalendar";
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 34ad54f9c65..e7138c41954 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -542,7 +542,7 @@ export const ensureUseDagServiceGetDagsUiData = (queryClient: QueryClient, { dag
 * Get latest run.
 * @param data The data for the request.
 * @param data.dagId
-* @returns DAGRunLightResponse Successful Response
+* @returns unknown Successful Response
 * @throws ApiError
 */
 export const ensureUseDagServiceGetLatestRunInfoData = (queryClient: QueryClient, { dagId }: {
@@ -1414,19 +1414,6 @@ export const ensureUseGridServiceGetGridTiSummariesData = (queryClient: QueryCli
   runId: string;
 }) => queryClient.ensureQueryData({ queryKey: Common.UseGridServiceGetGridTiSummariesKeyFn({ dagId, runId }), queryFn: () => GridService.getGridTiSummaries({ dagId, runId }) });
 /**
-* Get Latest Run
-* Get information about the latest dag run by run_after.
-*
-* This is used by the UI to figure out if it needs to rerun queries and resume auto refresh.
-* @param data The data for the request.
-* @param data.dagId
-* @returns unknown Successful Response
-* @throws ApiError
-*/
-export const ensureUseGridServiceGetLatestRunData = (queryClient: QueryClient, { dagId }: {
-  dagId: string;
-}) => queryClient.ensureQueryData({ queryKey: Common.UseGridServiceGetLatestRunKeyFn({ dagId }), queryFn: () => GridService.getLatestRun({ dagId }) });
-/**
 * Get Calendar
 * Get calendar data for a DAG including historical and planned DAG runs.
 * @param data The data for the request.
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 8fa4da8b39f..a0c05257f65 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -542,7 +542,7 @@ export const prefetchUseDagServiceGetDagsUi = (queryClient: QueryClient, { dagDi
 * Get latest run.
 * @param data The data for the request.
 * @param data.dagId
-* @returns DAGRunLightResponse Successful Response
+* @returns unknown Successful Response
 * @throws ApiError
 */
 export const prefetchUseDagServiceGetLatestRunInfo = (queryClient: QueryClient, { dagId }: {
@@ -1414,19 +1414,6 @@ export const prefetchUseGridServiceGetGridTiSummaries = (queryClient: QueryClien
   runId: string;
 }) => queryClient.prefetchQuery({ queryKey: Common.UseGridServiceGetGridTiSummariesKeyFn({ dagId, runId }), queryFn: () => GridService.getGridTiSummaries({ dagId, runId }) });
 /**
-* Get Latest Run
-* Get information about the latest dag run by run_after.
-*
-* This is used by the UI to figure out if it needs to rerun queries and resume auto refresh.
-* @param data The data for the request.
-* @param data.dagId
-* @returns unknown Successful Response
-* @throws ApiError
-*/
-export const prefetchUseGridServiceGetLatestRun = (queryClient: QueryClient, { dagId }: {
-  dagId: string;
-}) => queryClient.prefetchQuery({ queryKey: Common.UseGridServiceGetLatestRunKeyFn({ dagId }), queryFn: () => GridService.getLatestRun({ dagId }) });
-/**
 * Get Calendar
 * Get calendar data for a DAG including historical and planned DAG runs.
 * @param data The data for the request.
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 8e813d704b6..2fa2b17fefc 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -542,7 +542,7 @@ export const useDagServiceGetDagsUi = <TData = Common.DagServiceGetDagsUiDefault
 * Get latest run.
 * @param data The data for the request.
 * @param data.dagId
-* @returns DAGRunLightResponse Successful Response
+* @returns unknown Successful Response
 * @throws ApiError
 */
 export const useDagServiceGetLatestRunInfo = <TData = Common.DagServiceGetLatestRunInfoDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ dagId }: {
@@ -1414,19 +1414,6 @@ export const useGridServiceGetGridTiSummaries = <TData = Common.GridServiceGetGr
   runId: string;
 }, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseGridServiceGetGridTiSummariesKeyFn({ dagId, runId }, queryKey), queryFn: () => GridService.getGridTiSummaries({ dagId, runId }) as TData, ...options });
 /**
-* Get Latest Run
-* Get information about the latest dag run by run_after.
-*
-* This is used by the UI to figure out if it needs to rerun queries and resume auto refresh.
-* @param data The data for the request.
-* @param data.dagId
-* @returns unknown Successful Response
-* @throws ApiError
-*/
-export const useGridServiceGetLatestRun = <TData = Common.GridServiceGetLatestRunDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ dagId }: {
-  dagId: string;
-}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseGridServiceGetLatestRunKeyFn({ dagId }, queryKey), queryFn: () => GridService.getLatestRun({ dagId }) as TData, ...options });
-/**
 * Get Calendar
 * Get calendar data for a DAG including historical and planned DAG runs.
 * @param data The data for the request.
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 3ad13e7d9aa..3eb26bd5377 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -542,7 +542,7 @@ export const useDagServiceGetDagsUiSuspense = <TData = Common.DagServiceGetDagsU
 * Get latest run.
 * @param data The data for the request.
 * @param data.dagId
-* @returns DAGRunLightResponse Successful Response
+* @returns unknown Successful Response
 * @throws ApiError
 */
 export const useDagServiceGetLatestRunInfoSuspense = <TData = Common.DagServiceGetLatestRunInfoDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ dagId }: {
@@ -1414,19 +1414,6 @@ export const useGridServiceGetGridTiSummariesSuspense = <TData = Common.GridServ
   runId: string;
 }, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: Common.UseGridServiceGetGridTiSummariesKeyFn({ dagId, runId }, queryKey), queryFn: () => GridService.getGridTiSummaries({ dagId, runId }) as TData, ...options });
 /**
-* Get Latest Run
-* Get information about the latest dag run by run_after.
-*
-* This is used by the UI to figure out if it needs to rerun queries and resume auto refresh.
-* @param data The data for the request.
-* @param data.dagId
-* @returns unknown Successful Response
-* @throws ApiError
-*/
-export const useGridServiceGetLatestRunSuspense = <TData = Common.GridServiceGetLatestRunDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ dagId }: {
-  dagId: string;
-}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: Common.UseGridServiceGetLatestRunKeyFn({ dagId }, queryKey), queryFn: () => GridService.getLatestRun({ dagId }) as TData, ...options });
-/**
 * Get Calendar
 * Get calendar data for a DAG including historical and planned DAG runs.
 * @param data The data for the request.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
index 58fff0b2020..d836de1bfce 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts
@@ -6460,14 +6460,18 @@ the API server/Web UI can use this data to render connection form UI.`
 
 export const $DAGRunLightResponse = {
     properties: {
-        run_id: {
-            type: 'string',
-            title: 'Run Id'
+        id: {
+            type: 'integer',
+            title: 'Id'
         },
         dag_id: {
             type: 'string',
             title: 'Dag Id'
         },
+        run_id: {
+            type: 'string',
+            title: 'Run Id'
+        },
         logical_date: {
             anyOf: [
                 {
@@ -6514,7 +6518,7 @@ export const $DAGRunLightResponse = {
         }
     },
     type: 'object',
-    required: ['run_id', 'dag_id', 'logical_date', 'run_after', 'start_date', 'end_date', 'state'],
+    required: ['id', 'dag_id', 'run_id', 'logical_date', 'run_after', 'start_date', 'end_date', 'state'],
     title: 'DAGRunLightResponse',
     description: 'DAG Run serializer for responses.'
 } as const;
@@ -7085,32 +7089,6 @@ export const $HistoricalMetricDataResponse = {
     description: 'Historical Metric Data serializer for responses.'
 } as const;
 
-export const $LatestRunResponse = {
-    properties: {
-        id: {
-            type: 'integer',
-            title: 'Id'
-        },
-        dag_id: {
-            type: 'string',
-            title: 'Dag Id'
-        },
-        run_id: {
-            type: 'string',
-            title: 'Run Id'
-        },
-        run_after: {
-            type: 'string',
-            format: 'date-time',
-            title: 'Run After'
-        }
-    },
-    type: 'object',
-    required: ['id', 'dag_id', 'run_id', 'run_after'],
-    title: 'LatestRunResponse',
-    description: 'Base Node serializer for responses.'
-} as const;
-
 export const $LightGridTaskInstanceSummary = {
     properties: {
         task_id: {
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 15b5ab5b58c..1251d2f83d0 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
@@ -3,7 +3,7 @@
 import type { CancelablePromise } from './core/CancelablePromise';
 import { OpenAPI } from './core/OpenAPI';
 import { request as __request } from './core/request';
-import type { GetAssetsData, GetAssetsResponse, GetAssetAliasesData, GetAssetAliasesResponse, GetAssetAliasData, GetAssetAliasResponse, GetAssetEventsData, GetAssetEventsResponse, CreateAssetEventData, CreateAssetEventResponse, MaterializeAssetData, MaterializeAssetResponse, GetAssetQueuedEventsData, GetAssetQueuedEventsResponse, DeleteAssetQueuedEventsData, DeleteAssetQueuedEventsResponse, GetAssetData, GetAssetResponse, GetDagAssetQueuedEventsData, GetDagAssetQueuedEventsResponse, Dele [...]
+import type { GetAssetsData, GetAssetsResponse, GetAssetAliasesData, GetAssetAliasesResponse, GetAssetAliasData, GetAssetAliasResponse, GetAssetEventsData, GetAssetEventsResponse, CreateAssetEventData, CreateAssetEventResponse, MaterializeAssetData, MaterializeAssetResponse, GetAssetQueuedEventsData, GetAssetQueuedEventsResponse, DeleteAssetQueuedEventsData, DeleteAssetQueuedEventsResponse, GetAssetData, GetAssetResponse, GetDagAssetQueuedEventsData, GetDagAssetQueuedEventsResponse, Dele [...]
 
 export class AssetService {
     /**
@@ -1686,7 +1686,7 @@ export class DagService {
      * Get latest run.
      * @param data The data for the request.
      * @param data.dagId
-     * @returns DAGRunLightResponse Successful Response
+     * @returns unknown Successful Response
      * @throws ApiError
      */
     public static getLatestRunInfo(data: GetLatestRunInfoData): CancelablePromise<GetLatestRunInfoResponse> {
@@ -3872,31 +3872,6 @@ export class GridService {
         });
     }
     
-    /**
-     * Get Latest Run
-     * Get information about the latest dag run by run_after.
-     *
-     * This is used by the UI to figure out if it needs to rerun queries and resume auto refresh.
-     * @param data The data for the request.
-     * @param data.dagId
-     * @returns unknown Successful Response
-     * @throws ApiError
-     */
-    public static getLatestRun(data: GetLatestRunData): CancelablePromise<GetLatestRunResponse> {
-        return __request(OpenAPI, {
-            method: 'GET',
-            url: '/ui/grid/latest_run/{dag_id}',
-            path: {
-                dag_id: data.dagId
-            },
-            errors: {
-                400: 'Bad Request',
-                404: 'Not Found',
-                422: 'Validation Error'
-            }
-        });
-    }
-    
 }
 
 export class CalendarService {
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 6d49ffe2d43..47d4b65cd89 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
@@ -1687,8 +1687,9 @@ export type ConnectionHookMetaData = {
  * DAG Run serializer for responses.
  */
 export type DAGRunLightResponse = {
-    run_id: string;
+    id: number;
     dag_id: string;
+    run_id: string;
     logical_date: string | null;
     run_after: string;
     start_date: string | null;
@@ -1832,16 +1833,6 @@ export type HistoricalMetricDataResponse = {
     task_instance_states: TaskInstanceStateCount;
 };
 
-/**
- * Base Node serializer for responses.
- */
-export type LatestRunResponse = {
-    id: number;
-    dag_id: string;
-    run_id: string;
-    run_after: string;
-};
-
 /**
  * Task Instance Summary model for the Grid UI.
  */
@@ -2426,7 +2417,7 @@ export type GetLatestRunInfoData = {
     dagId: string;
 };
 
-export type GetLatestRunInfoResponse = Array<DAGRunLightResponse>;
+export type GetLatestRunInfoResponse = DAGRunLightResponse | null;
 
 export type GetEventLogData = {
     eventLogId: number;
@@ -3050,12 +3041,6 @@ export type GetGridTiSummariesData = {
 
 export type GetGridTiSummariesResponse = GridTISummaries;
 
-export type GetLatestRunData = {
-    dagId: string;
-};
-
-export type GetLatestRunResponse = LatestRunResponse | null;
-
 export type GetCalendarData = {
     dagId: string;
     granularity?: 'hourly' | 'daily';
@@ -4585,7 +4570,7 @@ export type $OpenApiTs = {
                 /**
                  * Successful Response
                  */
-                200: Array<DAGRunLightResponse>;
+                200: DAGRunLightResponse | null;
                 /**
                  * Not Found
                  */
@@ -6300,29 +6285,6 @@ export type $OpenApiTs = {
             };
         };
     };
-    '/ui/grid/latest_run/{dag_id}': {
-        get: {
-            req: GetLatestRunData;
-            res: {
-                /**
-                 * Successful Response
-                 */
-                200: LatestRunResponse | null;
-                /**
-                 * Bad Request
-                 */
-                400: HTTPExceptionResponse;
-                /**
-                 * Not Found
-                 */
-                404: HTTPExceptionResponse;
-                /**
-                 * Validation Error
-                 */
-                422: HTTPValidationError;
-            };
-        };
-    };
     '/ui/calendar/{dag_id}': {
         get: {
             req: GetCalendarData;
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
index 7f28dad5622..2857f4abd98 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
@@ -70,7 +70,7 @@ export const Dag = () => {
   const displayTabs = tabs.filter((tab) => !(dag?.timetable_summary === null && tab.value === "backfills"));
 
   const {
-    data: latestRuns,
+    data: latestRun,
     error: runsError,
     isLoading: isLoadingRuns,
   } = useDagServiceGetLatestRunInfo(
@@ -81,22 +81,22 @@ export const Dag = () => {
     {
       enabled: Boolean(dagId),
       refetchInterval: (query) => {
-        setHasPendingRuns(query.state.data?.some((run) => isStatePending(run.state)));
+        if (query.state.data && isStatePending(query.state.data.state)) {
+          setHasPendingRuns(true);
+        }
 
         return hasPendingRuns ? refetchInterval : false;
       },
     },
   );
 
-  const [latestRun] = latestRuns ?? [];
-
   return (
     <ReactFlowProvider>
       <DetailsLayout error={error ?? runsError} isLoading={isLoading || isLoadingRuns} tabs={displayTabs}>
         <Header
           dag={dag}
           isRefreshing={Boolean(isStatePending(latestRun?.state) && Boolean(refetchInterval))}
-          latestRunInfo={latestRun}
+          latestRunInfo={latestRun ?? undefined}
         />
       </DetailsLayout>
     </ReactFlowProvider>
diff --git a/airflow-core/src/airflow/ui/src/queries/useRefreshOnNewDagRuns.ts b/airflow-core/src/airflow/ui/src/queries/useRefreshOnNewDagRuns.ts
index d47f3a6e35b..5ceae8bbf69 100644
--- a/airflow-core/src/airflow/ui/src/queries/useRefreshOnNewDagRuns.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useRefreshOnNewDagRuns.ts
@@ -25,9 +25,9 @@ import {
   UseDagServiceGetDagDetailsKeyFn,
   useDagServiceGetDagsUi,
   UseTaskInstanceServiceGetTaskInstancesKeyFn,
-  useGridServiceGetLatestRun,
   UseGridServiceGetDagStructureKeyFn,
   UseGridServiceGetGridRunsKeyFn,
+  useDagServiceGetLatestRunInfo,
 } from "openapi/queries";
 
 import { useConfig } from "./useConfig";
@@ -37,14 +37,12 @@ export const useRefreshOnNewDagRuns = (dagId: string, hasPendingRuns: boolean |
   const previousDagRunIdRef = useRef<string>();
   const autoRefreshInterval = useConfig("auto_refresh_interval") as number;
 
-  const { data } = useGridServiceGetLatestRun({ dagId }, undefined, {
+  const { data: latestDagRun } = useDagServiceGetLatestRunInfo({ dagId }, undefined, {
     enabled: Boolean(dagId) && !hasPendingRuns,
     refetchInterval: Boolean(autoRefreshInterval) ? autoRefreshInterval * 1000 : 5000,
   });
 
   useEffect(() => {
-    const latestDagRun = data;
-
     const latestDagRunId = latestDagRun?.run_id;
 
     if ((latestDagRunId ?? "") && previousDagRunIdRef.current !== latestDagRunId) {
@@ -64,5 +62,5 @@ export const useRefreshOnNewDagRuns = (dagId: string, hasPendingRuns: boolean |
         void queryClient.invalidateQueries({ queryKey: key });
       });
     }
-  }, [data, dagId, queryClient]);
+  }, [latestDagRun, dagId, queryClient]);
 };
diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_dags.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_dags.py
index 860668c06b5..ce41173ee68 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_dags.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_dags.py
@@ -17,6 +17,7 @@
 from __future__ import annotations
 
 from datetime import datetime, timezone
+from unittest import mock
 
 import pendulum
 import pytest
@@ -114,3 +115,27 @@ class TestGetDagRuns(TestPublicDagEndpoint):
     def test_should_response_403(self, unauthorized_test_client):
         response = unauthorized_test_client.get("/dags", params={})
         assert response.status_code == 403
+
+    @pytest.mark.usefixtures("configure_git_connection_for_dag_bundle")
+    def test_latest_run_should_return_200(self, test_client):
+        response = test_client.get(f"/dags/{DAG1_ID}/latest_run")
+        assert response.status_code == 200
+        body = response.json()
+        assert body == {
+            "id": mock.ANY,
+            "dag_id": "test_dag1",
+            "run_id": "run_id_5",
+            "logical_date": "2025-01-01T00:00:00Z",
+            "run_after": "2025-01-01T00:00:00Z",
+            "start_date": "2025-01-01T00:00:00Z",
+            "end_date": "2025-01-01T01:00:00Z",
+            "state": "failed",
+        }
+
+    def test_latest_run_should_response_401(self, unauthenticated_test_client):
+        response = unauthenticated_test_client.get(f"/dags/{DAG1_ID}/latest_run")
+        assert response.status_code == 401
+
+    def test_latest_run_should_response_403(self, unauthorized_test_client):
+        response = unauthorized_test_client.get(f"/dags/{DAG1_ID}/latest_run")
+        assert response.status_code == 403
diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
index 96808bced38..d95d585e409 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
@@ -387,12 +387,12 @@ class TestGetGridDataEndpoint:
         assert response.status_code == 200
         assert response.json() == expected
 
-    @pytest.mark.parametrize("endpoint", ["runs", "structure", "latest_run"])
+    @pytest.mark.parametrize("endpoint", ["runs", "structure"])
     def test_should_response_401(self, unauthenticated_test_client, endpoint):
         response = unauthenticated_test_client.get(f"/grid/{endpoint}/{DAG_ID_3}")
         assert response.status_code == 401
 
-    @pytest.mark.parametrize("endpoint", ["runs", "structure", "latest_run"])
+    @pytest.mark.parametrize("endpoint", ["runs", "structure"])
     def test_should_response_403(self, unauthorized_test_client, endpoint):
         response = unauthorized_test_client.get(f"/grid/{endpoint}/{DAG_ID_3}")
         assert response.status_code == 403