From 7dc5c9ab28283927d45ea7d774e4ff0f7c87a037 Mon Sep 17 00:00:00 2001 From: pierrejeambrun Date: Wed, 9 Jul 2025 13:47:32 +0200 Subject: [PATCH 1/2] Fix no_status and duration for grid summaries --- .../core_api/datamodels/ui/common.py | 8 +- .../api_fastapi/core_api/routes/ui/grid.py | 1 - .../api_fastapi/core_api/services/ui/grid.py | 2 +- .../core_api/routes/ui/test_grid.py | 112 +++++++++++++++--- 4 files changed, 104 insertions(+), 19 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 0f315326194e5..4fedad08e1c27 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 @@ -23,6 +23,7 @@ from pydantic import computed_field from airflow.api_fastapi.core_api.base import BaseModel +from airflow.utils import timezone from airflow.utils.state import TaskInstanceState from airflow.utils.types import DagRunType @@ -81,9 +82,10 @@ class GridRunsResponse(BaseModel): @computed_field def duration(self) -> int | None: - if self.start_date and self.end_date: - return (self.end_date - self.start_date).seconds - return None + if self.start_date: + end_date = self.end_date or timezone.utcnow() + return (end_date - self.start_date).seconds + return 0 class BaseGraphResponse(BaseModel, Generic[E, N]): 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 095f0f6cce7b6..56118a9e71e0b 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 @@ -480,7 +480,6 @@ def get_grid_runs( ) ), ], - response_model_exclude_none=True, ) def get_grid_ti_summaries( dag_id: str, diff --git a/airflow-core/src/airflow/api_fastapi/core_api/services/ui/grid.py b/airflow-core/src/airflow/api_fastapi/core_api/services/ui/grid.py index a15ae5a3703e2..de92e6e6fa4e1 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/services/ui/grid.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/services/ui/grid.py @@ -326,7 +326,7 @@ def agg_state(states): def _get_aggs_for_node(detail): - states = [x["state"] for x in detail if x["state"] is not None] + states = [x["state"] for x in detail] try: min_start_date = min(x["start_date"] for x in detail if x["start_date"]) except ValueError: 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 97cff659aa12f..bf5635de295b6 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 @@ -1224,9 +1224,27 @@ def test_grid_ti_summaries_group(self, session, test_client): "dag_id": "test_dag_4", "run_id": "run_4-1", "task_instances": [ - {"state": "success", "task_id": "t1"}, - {"state": "success", "task_id": "t2"}, - {"state": "success", "task_id": "t7"}, + { + "state": "success", + "task_id": "t1", + "child_states": None, + "max_end_date": None, + "min_start_date": None, + }, + { + "state": "success", + "task_id": "t2", + "child_states": None, + "max_end_date": None, + "min_start_date": None, + }, + { + "state": "success", + "task_id": "t7", + "child_states": None, + "max_end_date": None, + "min_start_date": None, + }, { "child_states": {"success": 2}, "max_end_date": "2025-03-02T00:00:12Z", @@ -1234,7 +1252,13 @@ def test_grid_ti_summaries_group(self, session, test_client): "state": "success", "task_id": "task_group-1", }, - {"state": "success", "task_id": "task_group-1.t6"}, + { + "state": "success", + "task_id": "task_group-1.t6", + "child_states": None, + "max_end_date": None, + "min_start_date": None, + }, { "child_states": {"success": 3}, "max_end_date": "2025-03-02T00:00:12Z", @@ -1242,9 +1266,27 @@ def test_grid_ti_summaries_group(self, session, test_client): "state": "success", "task_id": "task_group-1.task_group-2", }, - {"state": "success", "task_id": "task_group-1.task_group-2.t3"}, - {"state": "success", "task_id": "task_group-1.task_group-2.t4"}, - {"state": "success", "task_id": "task_group-1.task_group-2.t5"}, + { + "state": "success", + "task_id": "task_group-1.task_group-2.t3", + "child_states": None, + "max_end_date": None, + "min_start_date": None, + }, + { + "state": "success", + "task_id": "task_group-1.task_group-2.t4", + "child_states": None, + "max_end_date": None, + "min_start_date": None, + }, + { + "state": "success", + "task_id": "task_group-1.task_group-2.t5", + "child_states": None, + "max_end_date": None, + "min_start_date": None, + }, ], } for obj in actual, expected: @@ -1269,7 +1311,13 @@ def sort_dict(in_dict): return out expected = [ - {"child_states": {}, "task_id": "mapped_task_2"}, + { + "child_states": {}, + "task_id": "mapped_task_2", + "max_end_date": None, + "min_start_date": None, + "state": None, + }, { "child_states": {"running": 1}, "max_end_date": "2024-12-30T01:02:03Z", @@ -1277,12 +1325,48 @@ def sort_dict(in_dict): "state": "running", "task_id": "mapped_task_group", }, - {"state": "running", "task_id": "mapped_task_group.subtask"}, - {"state": "success", "task_id": "task"}, - {"child_states": {}, "task_id": "task_group"}, - {"child_states": {}, "task_id": "task_group.inner_task_group"}, - {"child_states": {}, "task_id": "task_group.inner_task_group.inner_task_group_sub_task"}, - {"child_states": {}, "task_id": "task_group.mapped_task"}, + { + "state": "running", + "task_id": "mapped_task_group.subtask", + "child_states": None, + "max_end_date": None, + "min_start_date": None, + }, + { + "state": "success", + "task_id": "task", + "child_states": None, + "max_end_date": None, + "min_start_date": None, + }, + { + "child_states": {}, + "task_id": "task_group", + "max_end_date": None, + "min_start_date": None, + "state": None, + }, + { + "child_states": {}, + "task_id": "task_group.inner_task_group", + "max_end_date": None, + "min_start_date": None, + "state": None, + }, + { + "child_states": {}, + "task_id": "task_group.inner_task_group.inner_task_group_sub_task", + "max_end_date": None, + "min_start_date": None, + "state": None, + }, + { + "child_states": {}, + "task_id": "task_group.mapped_task", + "max_end_date": None, + "min_start_date": None, + "state": None, + }, ] expected = sort_dict(expected) actual = sort_dict(actual) From 2e84e45f3676e0c04989a3496e293b92214be7f4 Mon Sep 17 00:00:00 2001 From: pierrejeambrun Date: Wed, 9 Jul 2025 14:47:15 +0200 Subject: [PATCH 2/2] Fix CI --- .../airflow/api_fastapi/core_api/datamodels/ui/grid.py | 2 +- .../api_fastapi/core_api/openapi/_private_ui.yaml | 2 -- .../src/airflow/ui/openapi-gen/requests/schemas.gen.ts | 3 --- .../unit/api_fastapi/core_api/routes/ui/test_grid.py | 10 +++++----- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/grid.py b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/grid.py index e518963781856..6a00f1088cf32 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/grid.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/ui/grid.py @@ -44,7 +44,7 @@ class LightGridTaskInstanceSummary(BaseModel): task_id: str state: TaskInstanceState | None - child_states: dict[TaskInstanceState, int] | None + child_states: dict[TaskInstanceState | None, int] | None min_start_date: datetime | None max_end_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 a70f7e2f8e599..cb48bdad87b51 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 @@ -2122,8 +2122,6 @@ components: anyOf: - additionalProperties: type: integer - propertyNames: - $ref: '#/components/schemas/TaskInstanceState' type: object - type: 'null' title: Child States 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 5f7890a4739f7..522cb366a7493 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 @@ -7189,9 +7189,6 @@ export const $LightGridTaskInstanceSummary = { additionalProperties: { type: 'integer' }, - propertyNames: { - '$ref': '#/components/schemas/TaskInstanceState' - }, type: 'object' }, { 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 bf5635de295b6..cf2e561ec30bf 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 @@ -1312,7 +1312,7 @@ def sort_dict(in_dict): expected = [ { - "child_states": {}, + "child_states": {"None": 1}, "task_id": "mapped_task_2", "max_end_date": None, "min_start_date": None, @@ -1340,28 +1340,28 @@ def sort_dict(in_dict): "min_start_date": None, }, { - "child_states": {}, + "child_states": {"None": 2}, "task_id": "task_group", "max_end_date": None, "min_start_date": None, "state": None, }, { - "child_states": {}, + "child_states": {"None": 1}, "task_id": "task_group.inner_task_group", "max_end_date": None, "min_start_date": None, "state": None, }, { - "child_states": {}, + "child_states": {"None": 2}, "task_id": "task_group.inner_task_group.inner_task_group_sub_task", "max_end_date": None, "min_start_date": None, "state": None, }, { - "child_states": {}, + "child_states": {"None": 4}, "task_id": "task_group.mapped_task", "max_end_date": None, "min_start_date": None,