From 67e5cbd791c17e4bc77254073ec63a6225c561ad Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Thu, 11 Sep 2025 10:40:59 -0600 Subject: [PATCH 1/6] feat(hitl): add created_at to HITLDetail --- airflow-core/docs/img/airflow_erd.sha256 | 2 +- airflow-core/docs/img/airflow_erd.svg | 121 +++++++++--------- .../api_fastapi/core_api/datamodels/hitl.py | 1 + .../core_api/openapi/_private_ui.yaml | 5 + .../openapi/v2-rest-api-generated.yaml | 5 + .../core_api/routes/public/hitl.py | 1 + ...76_3_1_0_add_human_in_the_loop_response.py | 2 + airflow-core/src/airflow/models/hitl.py | 2 + .../ui/openapi-gen/requests/schemas.gen.ts | 7 +- .../ui/openapi-gen/requests/types.gen.ts | 1 + .../core_api/routes/public/test_hitl.py | 1 + .../core_api/routes/ui/test_dags.py | 1 + .../airflowctl/api/datamodels/generated.py | 1 + 13 files changed, 90 insertions(+), 60 deletions(-) diff --git a/airflow-core/docs/img/airflow_erd.sha256 b/airflow-core/docs/img/airflow_erd.sha256 index ba060d4f5eed7..bf88ae1d3c4a5 100644 --- a/airflow-core/docs/img/airflow_erd.sha256 +++ b/airflow-core/docs/img/airflow_erd.sha256 @@ -1 +1 @@ -35e9e07930e138664fb6ff23bc299567a88946734630d84f3d7d95deacf2f4b8 \ No newline at end of file +35b8a7f30e44075373199a53e6634693f4254287a9ecff0582d9ae926fc7aaae \ No newline at end of file diff --git a/airflow-core/docs/img/airflow_erd.svg b/airflow-core/docs/img/airflow_erd.svg index 6fbf9c224a465..c6fc5f0ea7d45 100644 --- a/airflow-core/docs/img/airflow_erd.svg +++ b/airflow-core/docs/img/airflow_erd.svg @@ -1437,68 +1437,73 @@ hitl_detail - -hitl_detail - -ti_id - - [UUID] - NOT NULL - -assignees - - [JSON] - -body - - [TEXT] - -chosen_options - - [JSON] - -defaults - - [JSON] - -multiple - - [BOOLEAN] - -options - - [JSON] - NOT NULL - -params - - [JSON] - NOT NULL - -params_input - - [JSON] - NOT NULL - -responded_at - - [TIMESTAMP] - -responded_by - - [JSON] - -subject - - [TEXT] - NOT NULL + +hitl_detail + +ti_id + + [UUID] + NOT NULL + +assignees + + [JSON] + +body + + [TEXT] + +chosen_options + + [JSON] + +created_at + + [TIMESTAMP] + NOT NULL + +defaults + + [JSON] + +multiple + + [BOOLEAN] + +options + + [JSON] + NOT NULL + +params + + [JSON] + NOT NULL + +params_input + + [JSON] + NOT NULL + +responded_at + + [TIMESTAMP] + +responded_by + + [JSON] + +subject + + [TEXT] + NOT NULL task_instance--hitl_detail - -1 -1 + +1 +1 diff --git a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/hitl.py b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/hitl.py index f24688c13e546..aa4f44f212bd9 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/datamodels/hitl.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/datamodels/hitl.py @@ -63,6 +63,7 @@ class HITLDetail(BaseModel): multiple: bool = False params: dict[str, Any] = Field(default_factory=dict) assigned_users: list[HITLUser] = Field(default_factory=list) + created_at: datetime # Response Content Detail responded_by_user: HITLUser | None = 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 269f60cd4c3a9..ec079113922db 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 @@ -1999,6 +1999,10 @@ components: $ref: '#/components/schemas/HITLUser' type: array title: Assigned Users + created_at: + type: string + format: date-time + title: Created At responded_by_user: anyOf: - $ref: '#/components/schemas/HITLUser' @@ -2029,6 +2033,7 @@ components: - task_instance - options - subject + - created_at title: HITLDetail description: Schema for Human-in-the-loop detail. HITLUser: 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 01b8ced149e92..e8f2eaf689bdb 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 @@ -10885,6 +10885,10 @@ components: $ref: '#/components/schemas/HITLUser' type: array title: Assigned Users + created_at: + type: string + format: date-time + title: Created At responded_by_user: anyOf: - $ref: '#/components/schemas/HITLUser' @@ -10915,6 +10919,7 @@ components: - task_instance - options - subject + - created_at title: HITLDetail description: Schema for Human-in-the-loop detail. HITLDetailCollection: diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/hitl.py b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/hitl.py index 60d9f99fab953..af20c10f29f01 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/hitl.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/hitl.py @@ -209,6 +209,7 @@ def get_hitl_details( "ti_id", "subject", "responded_at", + "created_at", ], model=HITLDetailModel, to_replace={ diff --git a/airflow-core/src/airflow/migrations/versions/0076_3_1_0_add_human_in_the_loop_response.py b/airflow-core/src/airflow/migrations/versions/0076_3_1_0_add_human_in_the_loop_response.py index cd875711ca4dd..3105d453e457e 100644 --- a/airflow-core/src/airflow/migrations/versions/0076_3_1_0_add_human_in_the_loop_response.py +++ b/airflow-core/src/airflow/migrations/versions/0076_3_1_0_add_human_in_the_loop_response.py @@ -32,6 +32,7 @@ from sqlalchemy import Boolean, Column, ForeignKeyConstraint, String, Text from sqlalchemy.dialects import postgresql +from airflow._shared.timezones import timezone from airflow.settings import json from airflow.utils.sqlalchemy import UtcDateTime @@ -60,6 +61,7 @@ def upgrade(): Column("multiple", Boolean, unique=False, default=False), Column("params", sqlalchemy_jsonfield.JSONField(json=json), nullable=False, default={}), Column("assignees", sqlalchemy_jsonfield.JSONField(json=json), nullable=True), + Column("created_at", UtcDateTime(timezone=True), nullable=False, default=timezone.utcnow), Column("responded_at", UtcDateTime, nullable=True), Column("responded_by", sqlalchemy_jsonfield.JSONField(json=json), nullable=True), Column("chosen_options", sqlalchemy_jsonfield.JSONField(json=json), nullable=True), diff --git a/airflow-core/src/airflow/models/hitl.py b/airflow-core/src/airflow/models/hitl.py index f13c4a6117f49..b6bbb2bc402b0 100644 --- a/airflow-core/src/airflow/models/hitl.py +++ b/airflow-core/src/airflow/models/hitl.py @@ -26,6 +26,7 @@ from sqlalchemy.orm import relationship from sqlalchemy.sql.functions import FunctionElement +from airflow._shared.timezones import timezone from airflow.models.base import Base from airflow.settings import json from airflow.utils.sqlalchemy import UtcDateTime @@ -97,6 +98,7 @@ class HITLDetail(Base): multiple = Column(Boolean, unique=False, default=False) params = Column(sqlalchemy_jsonfield.JSONField(json=json), nullable=False, default={}) assignees = Column(sqlalchemy_jsonfield.JSONField(json=json), nullable=True) + created_at = Column(UtcDateTime, default=timezone.utcnow, nullable=False) # Response Content Detail responded_at = Column(UtcDateTime, nullable=True) 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 1cf2a46b4611b..32dafcb90fddc 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 @@ -3578,6 +3578,11 @@ export const $HITLDetail = { type: 'array', title: 'Assigned Users' }, + created_at: { + type: 'string', + format: 'date-time', + title: 'Created At' + }, responded_by_user: { anyOf: [ { @@ -3626,7 +3631,7 @@ export const $HITLDetail = { } }, type: 'object', - required: ['task_instance', 'options', 'subject'], + required: ['task_instance', 'options', 'subject', 'created_at'], title: 'HITLDetail', description: 'Schema for Human-in-the-loop detail.' } 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 636c1b9b20b32..dbc2f4e8753d2 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 @@ -941,6 +941,7 @@ export type HITLDetail = { [key: string]: unknown; }; assigned_users?: Array; + created_at: string; responded_by_user?: HITLUser | null; responded_at?: string | null; chosen_options?: Array<(string)> | null; diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py index 61f6488d5fc68..517651a223416 100644 --- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py +++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py @@ -209,6 +209,7 @@ def expected_sample_hitl_detail_dict(sample_ti: TaskInstance) -> dict[str, Any]: "options": ["Approve", "Reject"], "params": {"input_1": 1}, "assigned_users": [], + "created_at": mock.ANY, "params_input": {}, "responded_at": None, "chosen_options": None, 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 d58f29839b6df..5ebf2221d92d2 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 @@ -188,6 +188,7 @@ def setup_hitl_data(self, create_task_instance: TaskInstance, session: Session): "params_input": {}, "response_received": False, "assigned_users": [], + "created_at": mock.ANY, } for i in range(3) ], diff --git a/airflow-ctl/src/airflowctl/api/datamodels/generated.py b/airflow-ctl/src/airflowctl/api/datamodels/generated.py index 31da4e1304429..739297fd43467 100644 --- a/airflow-ctl/src/airflowctl/api/datamodels/generated.py +++ b/airflow-ctl/src/airflowctl/api/datamodels/generated.py @@ -1837,6 +1837,7 @@ class HITLDetail(BaseModel): multiple: Annotated[bool | None, Field(title="Multiple")] = False params: Annotated[dict[str, Any] | None, Field(title="Params")] = None assigned_users: Annotated[list[HITLUser] | None, Field(title="Assigned Users")] = None + created_at: Annotated[datetime, Field(title="Created At")] responded_by_user: HITLUser | None = None responded_at: Annotated[datetime | None, Field(title="Responded At")] = None chosen_options: Annotated[list[str] | None, Field(title="Chosen Options")] = None From 8edeea8f8cc364c7586c6d4528f04a3f210085ac Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Thu, 11 Sep 2025 22:35:23 -0600 Subject: [PATCH 2/6] feat(hitl): add created_at to core-api filter --- .../core_api/routes/public/hitl.py | 4 +++ .../core_api/routes/public/test_hitl.py | 32 +++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/hitl.py b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/hitl.py index af20c10f29f01..f4b1e0baae26a 100644 --- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/hitl.py +++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/hitl.py @@ -38,7 +38,9 @@ QueryLimit, QueryOffset, QueryTIStateFilter, + RangeFilter, SortParam, + datetime_range_filter_factory, ) from airflow.api_fastapi.common.router import AirflowRouter from airflow.api_fastapi.core_api.datamodels.hitl import ( @@ -235,6 +237,7 @@ def get_hitl_details( responded_user_name: QueryHITLDetailRespondedUserNameFilter, subject_patten: QueryHITLDetailSubjectSearch, body_patten: QueryHITLDetailBodySearch, + created_at: Annotated[RangeFilter, Depends(datetime_range_filter_factory("created_at", HITLDetailModel))], ) -> HITLDetailCollection: """Get Human-in-the-loop details.""" query = ( @@ -266,6 +269,7 @@ def get_hitl_details( responded_user_name, subject_patten, body_patten, + created_at, ], offset=offset, limit=limit, diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py index 517651a223416..5a29190418439 100644 --- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py +++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py @@ -18,7 +18,7 @@ import json from collections.abc import Callable -from datetime import datetime +from datetime import datetime, timedelta from operator import itemgetter from typing import TYPE_CHECKING, Any from unittest import mock @@ -28,12 +28,14 @@ from sqlalchemy import select from sqlalchemy.orm import Session -from airflow._shared.timezones.timezone import utcnow +from airflow._shared.timezones.timezone import utc, utcnow from airflow.models.hitl import HITLDetail from airflow.models.log import Log from airflow.sdk.execution_time.hitl import HITLUser from airflow.utils.state import TaskInstanceState +from tests_common.test_utils.format_datetime import from_datetime_to_zulu_without_ms + if TYPE_CHECKING: from fastapi.testclient import TestClient @@ -49,6 +51,10 @@ TASK_ID = "sample_task_hitl" +DEFAULT_CREATED_AT = datetime(2025, 9, 15, 13, 0, 0, tzinfo=utc) +ANOTHER_CREATED_AT = datetime(2025, 9, 16, 12, 0, 0, tzinfo=utc) + + @pytest.fixture def sample_ti( create_task_instance: CreateTaskInstance, @@ -158,6 +164,7 @@ def sample_hitl_details(sample_tis: list[TaskInstance], session: Session) -> lis defaults=["Approve"], multiple=False, params={"input_1": 1}, + created_at=DEFAULT_CREATED_AT, ) for i, ti in enumerate(sample_tis[:5]) ] @@ -175,6 +182,7 @@ def sample_hitl_details(sample_tis: list[TaskInstance], session: Session) -> lis chosen_options=[str(i)], params_input={"input": i}, responded_by={"id": "test", "name": "test"}, + created_at=ANOTHER_CREATED_AT, ) for i, ti in enumerate(sample_tis[5:]) ] @@ -543,6 +551,21 @@ def test_should_respond_200_with_existing_response( ({"response_received": True}, 3), ({"responded_by_user_id": ["test"]}, 3), ({"responded_by_user_name": ["test"]}, 3), + ( + {"created_at_gte": from_datetime_to_zulu_without_ms(DEFAULT_CREATED_AT + timedelta(days=1))}, + 0, + ), + ( + {"created_at_lte": from_datetime_to_zulu_without_ms(DEFAULT_CREATED_AT - timedelta(days=1))}, + 0, + ), + ( + { + "created_at_gte": from_datetime_to_zulu_without_ms(DEFAULT_CREATED_AT), + "created_at_lte": from_datetime_to_zulu_without_ms(DEFAULT_CREATED_AT), + }, + 5, + ), ], ids=[ "dag_id_pattern_hitl_dag", @@ -557,6 +580,9 @@ def test_should_respond_200_with_existing_response( "response_received", "responded_user_id", "responded_user_name", + "created_at_gte", + "created_at_lte", + "created_at", ], ) def test_should_respond_200_with_existing_response_and_query( @@ -613,6 +639,7 @@ def test_should_respond_200_with_existing_response_and_concrete_query( # htil key ("subject", itemgetter("subject")), ("responded_at", itemgetter("responded_at")), + ("created_at", itemgetter("response_at")), ], ids=[ "ti_id", @@ -623,6 +650,7 @@ def test_should_respond_200_with_existing_response_and_concrete_query( "task_instance_operator", "subject", "responded_at", + "created_at", ], ) def test_should_respond_200_with_existing_response_and_order_by( From 48cb8a99d3ab4639784bf3a69e3877e3500ed54e Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Fri, 12 Sep 2025 22:27:17 -0700 Subject: [PATCH 3/6] fixup! feat(hitl): add created_at to core-api filter --- .../openapi/v2-rest-api-generated.yaml | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) 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 e8f2eaf689bdb..95fa0fcda547f 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 @@ -8264,6 +8264,42 @@ paths: title: Body Search description: "SQL LIKE expression \u2014 use `%` / `_` wildcards (e.g. `%customer_%`).\ \ Regular expressions are **not** supported." + - name: created_at_gte + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Created At Gte + - name: created_at_gt + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Created At Gt + - name: created_at_lte + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Created At Lte + - name: created_at_lt + in: query + required: false + schema: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Created At Lt responses: '200': description: Successful Response From 369645246205039d92e7aeab2fc539427bdab5fc Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Tue, 16 Sep 2025 10:06:26 +0900 Subject: [PATCH 4/6] fixup! fixup! feat(hitl): add created_at to core-api filter --- .../src/airflow/ui/openapi-gen/queries/common.ts | 8 ++++++-- .../ui/openapi-gen/queries/ensureQueryData.ts | 12 ++++++++++-- .../src/airflow/ui/openapi-gen/queries/prefetch.ts | 12 ++++++++++-- .../src/airflow/ui/openapi-gen/queries/queries.ts | 12 ++++++++++-- .../src/airflow/ui/openapi-gen/queries/suspense.ts | 12 ++++++++++-- .../airflow/ui/openapi-gen/requests/services.gen.ts | 10 +++++++++- .../src/airflow/ui/openapi-gen/requests/types.gen.ts | 4 ++++ 7 files changed, 59 insertions(+), 11 deletions(-) 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 601239c7c7230..874ff6b4264cf 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts @@ -567,8 +567,12 @@ export const UseTaskInstanceServiceGetHitlDetailKeyFn = ({ dagId, dagRunId, mapI export type TaskInstanceServiceGetHitlDetailsDefaultResponse = Awaited>; export type TaskInstanceServiceGetHitlDetailsQueryResult = UseQueryResult; export const useTaskInstanceServiceGetHitlDetailsKey = "TaskInstanceServiceGetHitlDetails"; -export const UseTaskInstanceServiceGetHitlDetailsKeyFn = ({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: { +export const UseTaskInstanceServiceGetHitlDetailsKeyFn = ({ bodySearch, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: { bodySearch?: string; + createdAtGt?: string; + createdAtGte?: string; + createdAtLt?: string; + createdAtLte?: string; dagId: string; dagIdPattern?: string; dagRunId: string; @@ -582,7 +586,7 @@ export const UseTaskInstanceServiceGetHitlDetailsKeyFn = ({ bodySearch, dagId, d subjectSearch?: string; taskId?: string; taskIdPattern?: string; -}, queryKey?: Array) => [useTaskInstanceServiceGetHitlDetailsKey, ...(queryKey ?? [{ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }])]; +}, queryKey?: Array) => [useTaskInstanceServiceGetHitlDetailsKey, ...(queryKey ?? [{ bodySearch, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }])]; export type ImportErrorServiceGetImportErrorDefaultResponse = Awaited>; export type ImportErrorServiceGetImportErrorQueryResult = UseQueryResult; export const useImportErrorServiceGetImportErrorKey = "ImportErrorServiceGetImportError"; 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 a58b538c48991..54cf0dca70953 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts @@ -1089,11 +1089,19 @@ export const ensureUseTaskInstanceServiceGetHitlDetailData = (queryClient: Query * @param data.respondedByUserName * @param data.subjectSearch SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). Regular expressions are **not** supported. * @param data.bodySearch SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). Regular expressions are **not** supported. +* @param data.createdAtGte +* @param data.createdAtGt +* @param data.createdAtLte +* @param data.createdAtLt * @returns HITLDetailCollection Successful Response * @throws ApiError */ -export const ensureUseTaskInstanceServiceGetHitlDetailsData = (queryClient: QueryClient, { bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: { +export const ensureUseTaskInstanceServiceGetHitlDetailsData = (queryClient: QueryClient, { bodySearch, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: { bodySearch?: string; + createdAtGt?: string; + createdAtGte?: string; + createdAtLt?: string; + createdAtLte?: string; dagId: string; dagIdPattern?: string; dagRunId: string; @@ -1107,7 +1115,7 @@ export const ensureUseTaskInstanceServiceGetHitlDetailsData = (queryClient: Quer subjectSearch?: string; taskId?: string; taskIdPattern?: string; -}) => queryClient.ensureQueryData({ queryKey: Common.UseTaskInstanceServiceGetHitlDetailsKeyFn({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }), queryFn: () => TaskInstanceService.getHitlDetails({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }) }); +}) => queryClient.ensureQueryData({ queryKey: Common.UseTaskInstanceServiceGetHitlDetailsKeyFn({ bodySearch, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }), queryFn: () => TaskInstanceService.getHitlDetails({ bodySearch, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }) }); /** * Get Import Error * Get an import error. 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 28cbad744a8a7..66a08f901b3ef 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts @@ -1089,11 +1089,19 @@ export const prefetchUseTaskInstanceServiceGetHitlDetail = (queryClient: QueryCl * @param data.respondedByUserName * @param data.subjectSearch SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). Regular expressions are **not** supported. * @param data.bodySearch SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). Regular expressions are **not** supported. +* @param data.createdAtGte +* @param data.createdAtGt +* @param data.createdAtLte +* @param data.createdAtLt * @returns HITLDetailCollection Successful Response * @throws ApiError */ -export const prefetchUseTaskInstanceServiceGetHitlDetails = (queryClient: QueryClient, { bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: { +export const prefetchUseTaskInstanceServiceGetHitlDetails = (queryClient: QueryClient, { bodySearch, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: { bodySearch?: string; + createdAtGt?: string; + createdAtGte?: string; + createdAtLt?: string; + createdAtLte?: string; dagId: string; dagIdPattern?: string; dagRunId: string; @@ -1107,7 +1115,7 @@ export const prefetchUseTaskInstanceServiceGetHitlDetails = (queryClient: QueryC subjectSearch?: string; taskId?: string; taskIdPattern?: string; -}) => queryClient.prefetchQuery({ queryKey: Common.UseTaskInstanceServiceGetHitlDetailsKeyFn({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }), queryFn: () => TaskInstanceService.getHitlDetails({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }) }); +}) => queryClient.prefetchQuery({ queryKey: Common.UseTaskInstanceServiceGetHitlDetailsKeyFn({ bodySearch, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }), queryFn: () => TaskInstanceService.getHitlDetails({ bodySearch, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }) }); /** * Get Import Error * Get an import error. 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 86843abd84c59..9c04bac85d4da 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts @@ -1089,11 +1089,19 @@ export const useTaskInstanceServiceGetHitlDetail = = unknown[]>({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: { +export const useTaskInstanceServiceGetHitlDetails = = unknown[]>({ bodySearch, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: { bodySearch?: string; + createdAtGt?: string; + createdAtGte?: string; + createdAtLt?: string; + createdAtLte?: string; dagId: string; dagIdPattern?: string; dagRunId: string; @@ -1107,7 +1115,7 @@ export const useTaskInstanceServiceGetHitlDetails = , "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseTaskInstanceServiceGetHitlDetailsKeyFn({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }, queryKey), queryFn: () => TaskInstanceService.getHitlDetails({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }) as TData, ...options }); +}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useQuery({ queryKey: Common.UseTaskInstanceServiceGetHitlDetailsKeyFn({ bodySearch, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }, queryKey), queryFn: () => TaskInstanceService.getHitlDetails({ bodySearch, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }) as TData, ...options }); /** * Get Import Error * Get an import error. 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 aaed59782dfb9..ba7f0fc93c985 100644 --- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts +++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts @@ -1089,11 +1089,19 @@ export const useTaskInstanceServiceGetHitlDetailSuspense = = unknown[]>({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: { +export const useTaskInstanceServiceGetHitlDetailsSuspense = = unknown[]>({ bodySearch, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: { bodySearch?: string; + createdAtGt?: string; + createdAtGte?: string; + createdAtLt?: string; + createdAtLte?: string; dagId: string; dagIdPattern?: string; dagRunId: string; @@ -1107,7 +1115,7 @@ export const useTaskInstanceServiceGetHitlDetailsSuspense = , "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseTaskInstanceServiceGetHitlDetailsKeyFn({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }, queryKey), queryFn: () => TaskInstanceService.getHitlDetails({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }) as TData, ...options }); +}, queryKey?: TQueryKey, options?: Omit, "queryKey" | "queryFn">) => useSuspenseQuery({ queryKey: Common.UseTaskInstanceServiceGetHitlDetailsKeyFn({ bodySearch, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }, queryKey), queryFn: () => TaskInstanceService.getHitlDetails({ bodySearch, createdAtGt, createdAtGte, createdAtLt, createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }) as TData, ...options }); /** * Get Import Error * Get an import error. 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 4efc34fabda66..8b4cb8c21e4a9 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 @@ -2798,6 +2798,10 @@ export class TaskInstanceService { * @param data.respondedByUserName * @param data.subjectSearch SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). Regular expressions are **not** supported. * @param data.bodySearch SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). Regular expressions are **not** supported. + * @param data.createdAtGte + * @param data.createdAtGt + * @param data.createdAtLte + * @param data.createdAtLt * @returns HITLDetailCollection Successful Response * @throws ApiError */ @@ -2821,7 +2825,11 @@ export class TaskInstanceService { responded_by_user_id: data.respondedByUserId, responded_by_user_name: data.respondedByUserName, subject_search: data.subjectSearch, - body_search: data.bodySearch + body_search: data.bodySearch, + created_at_gte: data.createdAtGte, + created_at_gt: data.createdAtGt, + created_at_lte: data.createdAtLte, + created_at_lt: data.createdAtLt }, errors: { 401: 'Unauthorized', 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 dbc2f4e8753d2..256812659a84e 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 @@ -2858,6 +2858,10 @@ export type GetHitlDetailsData = { * SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). Regular expressions are **not** supported. */ bodySearch?: string | null; + createdAtGt?: string | null; + createdAtGte?: string | null; + createdAtLt?: string | null; + createdAtLte?: string | null; dagId: string; /** * SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). Regular expressions are **not** supported. From 3a05d9f31507c04c9bcc083703e9ca5a63b65992 Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Tue, 16 Sep 2025 16:59:48 +0900 Subject: [PATCH 5/6] fixup! fixup! fixup! feat(hitl): add created_at to core-api filter --- .../tests/unit/api_fastapi/core_api/routes/public/test_hitl.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py index 5a29190418439..473ca8a6846a1 100644 --- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py +++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py @@ -614,6 +614,7 @@ def test_should_respond_200_with_existing_response_and_concrete_query( "multiple": False, "params": {"input_1": 1}, "assigned_users": [], + "created_at": DEFAULT_CREATED_AT, "responded_by_user": None, "responded_at": None, "chosen_options": None, @@ -639,7 +640,7 @@ def test_should_respond_200_with_existing_response_and_concrete_query( # htil key ("subject", itemgetter("subject")), ("responded_at", itemgetter("responded_at")), - ("created_at", itemgetter("response_at")), + ("created_at", itemgetter("created_at")), ], ids=[ "ti_id", From 00f24ead3600280d0ac41b23413e67b6e996d37d Mon Sep 17 00:00:00 2001 From: Wei Lee Date: Tue, 16 Sep 2025 17:06:23 +0900 Subject: [PATCH 6/6] fixup! fixup! fixup! fixup! feat(hitl): add created_at to core-api filter --- .../tests/unit/api_fastapi/core_api/routes/public/test_hitl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py index 473ca8a6846a1..f8ab8e1c4d9a4 100644 --- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py +++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py @@ -614,7 +614,7 @@ def test_should_respond_200_with_existing_response_and_concrete_query( "multiple": False, "params": {"input_1": 1}, "assigned_users": [], - "created_at": DEFAULT_CREATED_AT, + "created_at": DEFAULT_CREATED_AT.isoformat().replace("+00:00", "Z"), "responded_by_user": None, "responded_at": None, "chosen_options": None,