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 2dee03e6510cc..31017a8307e05 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 @@ -20,7 +20,7 @@ from datetime import datetime from typing import Generic, Literal, TypeVar -from pydantic import computed_field +from pydantic import AliasPath, Field, computed_field from airflow._shared.timezones import timezone from airflow.api_fastapi.core_api.base import BaseModel @@ -79,6 +79,7 @@ class GridRunsResponse(BaseModel): run_after: datetime state: TaskInstanceState | None run_type: DagRunType + dag_display_name: str = Field(validation_alias=AliasPath("dag_model", "dag_display_name")) @computed_field def duration(self) -> int: 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 88c840e3f0aaf..66e1921c785ac 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 @@ -1778,6 +1778,9 @@ components: - type: 'null' run_type: $ref: '#/components/schemas/DagRunType' + dag_display_name: + type: string + title: Dag Display Name duration: type: integer title: Duration @@ -1792,6 +1795,7 @@ components: - run_after - state - run_type + - dag_display_name - duration title: GridRunsResponse description: Base Node serializer for responses. 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 fd217f3fbdf9b..b8ed6e80d53ff 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 @@ -23,6 +23,7 @@ import structlog from fastapi import Depends, HTTPException, status from sqlalchemy import select +from sqlalchemy.orm import joinedload from airflow.api_fastapi.auth.managers.models.resource_details import DagAccessEntity from airflow.api_fastapi.common.db.common import SessionDep, paginated_select @@ -217,16 +218,7 @@ def get_grid_runs( ) -> list[GridRunsResponse]: """Get info about a run for the grid.""" # Retrieve, sort the previous DAG Runs - base_query = select( - DagRun.dag_id, - DagRun.run_id, - DagRun.queued_at, - DagRun.start_date, - DagRun.end_date, - DagRun.run_after, - DagRun.state, - DagRun.run_type, - ).where(DagRun.dag_id == dag_id) + base_query = select(DagRun).options(joinedload(DagRun.dag_model)).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()]: @@ -244,7 +236,7 @@ def get_grid_runs( filters=[run_after], limit=limit, ) - return session.execute(dag_runs_select_filter) + return session.scalars(dag_runs_select_filter) @grid_router.get( 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 204d2439c29fb..eb310636b4a33 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 @@ -7102,6 +7102,10 @@ export const $GridRunsResponse = { run_type: { '$ref': '#/components/schemas/DagRunType' }, + dag_display_name: { + type: 'string', + title: 'Dag Display Name' + }, duration: { type: 'integer', title: 'Duration', @@ -7109,7 +7113,7 @@ export const $GridRunsResponse = { } }, type: 'object', - required: ['dag_id', 'run_id', 'queued_at', 'start_date', 'end_date', 'run_after', 'state', 'run_type', 'duration'], + required: ['dag_id', 'run_id', 'queued_at', 'start_date', 'end_date', 'run_after', 'state', 'run_type', 'dag_display_name', 'duration'], title: 'GridRunsResponse', description: 'Base Node serializer for responses.' } as const; 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 2ed7cb779855c..b036db95173fc 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 @@ -1817,6 +1817,7 @@ export type GridRunsResponse = { run_after: string; state: TaskInstanceState | null; run_type: DagRunType; + dag_display_name: string; readonly duration: number; }; 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 271d90c154b72..a36e0a353adc8 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 @@ -44,6 +44,10 @@ DAG_ID_2 = "test_dag_2" DAG_ID_3 = "test_dag_3" DAG_ID_4 = "test_dag_4" +DAG_DISPLAY_NAME = "test_dag_display_name" +DAG_DISPLAY_NAME_2 = "test_dag_display_name_2" +DAG_DISPLAY_NAME_3 = "test_dag_display_name_3" +DAG_DISPLAY_NAME_4 = "test_dag_display_name_4" TASK_ID = "task" TASK_ID_2 = "task2" TASK_ID_3 = "task3" @@ -57,6 +61,7 @@ GRID_RUN_1 = { "dag_id": "test_dag", + "dag_display_name": "test_dag_display_name", "duration": 0, "end_date": "2024-12-31T00:00:00Z", "run_after": "2024-11-30T00:00:00Z", @@ -68,6 +73,7 @@ GRID_RUN_2 = { "dag_id": "test_dag", + "dag_display_name": "test_dag_display_name", "duration": 0, "end_date": "2024-12-31T00:00:00Z", "run_after": "2024-11-30T00:00:00Z", @@ -120,7 +126,7 @@ def setup(dag_maker, session=None): clear_db_serialized_dags() # DAG 1 - with dag_maker(dag_id=DAG_ID, serialized=True, session=session) as dag: + with dag_maker(dag_id=DAG_ID, serialized=True, session=session, dag_display_name=DAG_DISPLAY_NAME) as dag: task = EmptyOperator(task_id=TASK_ID) @task_group @@ -172,11 +178,13 @@ def mapped_task_group(arg1): ti.end_date = None # DAG 2 - with dag_maker(dag_id=DAG_ID_2, serialized=True, session=session): + with dag_maker(dag_id=DAG_ID_2, serialized=True, session=session, dag_display_name=DAG_DISPLAY_NAME_2): EmptyOperator(task_id=TASK_ID_2) # DAG 3 for testing removed task - with dag_maker(dag_id=DAG_ID_3, serialized=True, session=session) as dag_3: + with dag_maker( + dag_id=DAG_ID_3, serialized=True, session=session, dag_display_name=DAG_DISPLAY_NAME_3 + ) as dag_3: EmptyOperator(task_id=TASK_ID_3) EmptyOperator(task_id=TASK_ID_4) with TaskGroup(group_id=TASK_GROUP_ID): @@ -195,7 +203,7 @@ def mapped_task_group(arg1): ) # Serialize DAG with only one task - with dag_maker(dag_id=DAG_ID_3, serialized=True, session=session): + with dag_maker(dag_id=DAG_ID_3, serialized=True, session=session, dag_display_name=DAG_DISPLAY_NAME_3): EmptyOperator(task_id=TASK_ID_3) run_4 = dag_maker.create_dagrun( @@ -216,7 +224,9 @@ def mapped_task_group(arg1): ti.end_date = None # DAG 4 for testing removed task - with dag_maker(dag_id=DAG_ID_4, serialized=True, session=session) as dag_4: + with dag_maker( + dag_id=DAG_ID_4, serialized=True, session=session, dag_display_name=DAG_DISPLAY_NAME_4 + ) as dag_4: t1 = EmptyOperator(task_id="t1") t2 = EmptyOperator(task_id="t2") with TaskGroup(group_id=f"{TASK_GROUP_ID}-1") as tg1: @@ -248,6 +258,7 @@ def mapped_task_group(arg1): ti.end_date = end_date start_date = end_date end_date = start_date.add(seconds=2) + session.commit() @@ -474,6 +485,7 @@ def test_get_grid_runs(self, session, test_client): assert response.json() == [ { "dag_id": "test_dag", + "dag_display_name": "test_dag_display_name", "duration": 0, "end_date": "2024-12-31T00:00:00Z", "run_after": "2024-11-30T00:00:00Z", @@ -484,6 +496,7 @@ def test_get_grid_runs(self, session, test_client): }, { "dag_id": "test_dag", + "dag_display_name": "test_dag_display_name", "duration": 0, "end_date": "2024-12-31T00:00:00Z", "run_after": "2024-11-30T00:00:00Z",