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 bfc0f894046 Add HITLDetail.created_at (#55525)
bfc0f894046 is described below
commit bfc0f8940461eae3b85196e82818c6b9d4bbe376
Author: Wei Lee <[email protected]>
AuthorDate: Tue Sep 16 17:59:51 2025 +0800
Add HITLDetail.created_at (#55525)
---
airflow-core/docs/img/airflow_erd.sha256 | 2 +-
airflow-core/docs/img/airflow_erd.svg | 121 +++++++++++----------
.../api_fastapi/core_api/datamodels/hitl.py | 1 +
.../api_fastapi/core_api/openapi/_private_ui.yaml | 5 +
.../core_api/openapi/v2-rest-api-generated.yaml | 41 +++++++
.../api_fastapi/core_api/routes/public/hitl.py | 5 +
.../0076_3_1_0_add_human_in_the_loop_response.py | 2 +
airflow-core/src/airflow/models/hitl.py | 2 +
.../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/schemas.gen.ts | 7 +-
.../ui/openapi-gen/requests/services.gen.ts | 10 +-
.../airflow/ui/openapi-gen/requests/types.gen.ts | 5 +
.../core_api/routes/public/test_hitl.py | 34 +++++-
.../api_fastapi/core_api/routes/ui/test_dags.py | 1 +
.../src/airflowctl/api/datamodels/generated.py | 1 +
19 files changed, 220 insertions(+), 73 deletions(-)
diff --git a/airflow-core/docs/img/airflow_erd.sha256
b/airflow-core/docs/img/airflow_erd.sha256
index ba060d4f5ee..bf88ae1d3c4 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 6fbf9c224a4..c6fc5f0ea7d 100644
--- a/airflow-core/docs/img/airflow_erd.svg
+++ b/airflow-core/docs/img/airflow_erd.svg
@@ -1437,68 +1437,73 @@
<!-- hitl_detail -->
<g id="node43" class="node">
<title>hitl_detail</title>
-<polygon fill="none" stroke="black" points="2197,-2304 2197,-2332 2435,-2332
2435,-2304 2197,-2304"/>
-<text text-anchor="start" x="2272" y="-2315.2"
font-family="Helvetica,sans-Serif" font-weight="bold"
font-size="16.00">hitl_detail</text>
-<polygon fill="none" stroke="black" points="2197,-2279 2197,-2304 2435,-2304
2435,-2279 2197,-2279"/>
-<text text-anchor="start" x="2202" y="-2288.8"
font-family="Helvetica,sans-Serif" text-decoration="underline"
font-size="14.00">ti_id</text>
-<text text-anchor="start" x="2231" y="-2288.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2236" y="-2288.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [UUID]</text>
-<text text-anchor="start" x="2288" y="-2288.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
-<polygon fill="none" stroke="black" points="2197,-2254 2197,-2279 2435,-2279
2435,-2254 2197,-2254"/>
-<text text-anchor="start" x="2202" y="-2263.8"
font-family="Helvetica,sans-Serif" font-size="14.00">assignees</text>
-<text text-anchor="start" x="2272" y="-2263.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2277" y="-2263.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
-<polygon fill="none" stroke="black" points="2197,-2229 2197,-2254 2435,-2254
2435,-2229 2197,-2229"/>
-<text text-anchor="start" x="2202" y="-2238.8"
font-family="Helvetica,sans-Serif" font-size="14.00">body</text>
-<text text-anchor="start" x="2237" y="-2238.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2242" y="-2238.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [TEXT]</text>
-<polygon fill="none" stroke="black" points="2197,-2204 2197,-2229 2435,-2229
2435,-2204 2197,-2204"/>
-<text text-anchor="start" x="2202" y="-2213.8"
font-family="Helvetica,sans-Serif" font-size="14.00">chosen_options</text>
-<text text-anchor="start" x="2310" y="-2213.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2315" y="-2213.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
-<polygon fill="none" stroke="black" points="2197,-2179 2197,-2204 2435,-2204
2435,-2179 2197,-2179"/>
-<text text-anchor="start" x="2202" y="-2188.8"
font-family="Helvetica,sans-Serif" font-size="14.00">defaults</text>
-<text text-anchor="start" x="2259" y="-2188.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2264" y="-2188.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
-<polygon fill="none" stroke="black" points="2197,-2154 2197,-2179 2435,-2179
2435,-2154 2197,-2154"/>
-<text text-anchor="start" x="2202" y="-2163.8"
font-family="Helvetica,sans-Serif" font-size="14.00">multiple</text>
-<text text-anchor="start" x="2259" y="-2163.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2264" y="-2163.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [BOOLEAN]</text>
-<polygon fill="none" stroke="black" points="2197,-2129 2197,-2154 2435,-2154
2435,-2129 2197,-2129"/>
-<text text-anchor="start" x="2202" y="-2138.8"
font-family="Helvetica,sans-Serif" font-size="14.00">options</text>
-<text text-anchor="start" x="2254" y="-2138.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2259" y="-2138.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
-<text text-anchor="start" x="2310" y="-2138.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
-<polygon fill="none" stroke="black" points="2197,-2104 2197,-2129 2435,-2129
2435,-2104 2197,-2104"/>
-<text text-anchor="start" x="2202" y="-2113.8"
font-family="Helvetica,sans-Serif" font-size="14.00">params</text>
-<text text-anchor="start" x="2255" y="-2113.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2260" y="-2113.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
-<text text-anchor="start" x="2311" y="-2113.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
-<polygon fill="none" stroke="black" points="2197,-2079 2197,-2104 2435,-2104
2435,-2079 2197,-2079"/>
-<text text-anchor="start" x="2202" y="-2088.8"
font-family="Helvetica,sans-Serif" font-size="14.00">params_input</text>
-<text text-anchor="start" x="2298" y="-2088.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2303" y="-2088.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
-<text text-anchor="start" x="2354" y="-2088.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
-<polygon fill="none" stroke="black" points="2197,-2054 2197,-2079 2435,-2079
2435,-2054 2197,-2054"/>
-<text text-anchor="start" x="2202" y="-2063.8"
font-family="Helvetica,sans-Serif" font-size="14.00">responded_at</text>
-<text text-anchor="start" x="2296" y="-2063.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2301" y="-2063.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [TIMESTAMP]</text>
-<polygon fill="none" stroke="black" points="2197,-2029 2197,-2054 2435,-2054
2435,-2029 2197,-2029"/>
-<text text-anchor="start" x="2202" y="-2038.8"
font-family="Helvetica,sans-Serif" font-size="14.00">responded_by</text>
-<text text-anchor="start" x="2300" y="-2038.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2305" y="-2038.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
-<polygon fill="none" stroke="black" points="2197,-2004 2197,-2029 2435,-2029
2435,-2004 2197,-2004"/>
-<text text-anchor="start" x="2202" y="-2013.8"
font-family="Helvetica,sans-Serif" font-size="14.00">subject</text>
-<text text-anchor="start" x="2253" y="-2013.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2258" y="-2013.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [TEXT]</text>
-<text text-anchor="start" x="2308" y="-2013.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
+<polygon fill="none" stroke="black" points="2186,-2328 2186,-2356 2446,-2356
2446,-2328 2186,-2328"/>
+<text text-anchor="start" x="2272" y="-2339.2"
font-family="Helvetica,sans-Serif" font-weight="bold"
font-size="16.00">hitl_detail</text>
+<polygon fill="none" stroke="black" points="2186,-2303 2186,-2328 2446,-2328
2446,-2303 2186,-2303"/>
+<text text-anchor="start" x="2191" y="-2312.8"
font-family="Helvetica,sans-Serif" text-decoration="underline"
font-size="14.00">ti_id</text>
+<text text-anchor="start" x="2220" y="-2312.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2225" y="-2312.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [UUID]</text>
+<text text-anchor="start" x="2277" y="-2312.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
+<polygon fill="none" stroke="black" points="2186,-2278 2186,-2303 2446,-2303
2446,-2278 2186,-2278"/>
+<text text-anchor="start" x="2191" y="-2287.8"
font-family="Helvetica,sans-Serif" font-size="14.00">assignees</text>
+<text text-anchor="start" x="2261" y="-2287.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2266" y="-2287.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
+<polygon fill="none" stroke="black" points="2186,-2253 2186,-2278 2446,-2278
2446,-2253 2186,-2253"/>
+<text text-anchor="start" x="2191" y="-2262.8"
font-family="Helvetica,sans-Serif" font-size="14.00">body</text>
+<text text-anchor="start" x="2226" y="-2262.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2231" y="-2262.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [TEXT]</text>
+<polygon fill="none" stroke="black" points="2186,-2228 2186,-2253 2446,-2253
2446,-2228 2186,-2228"/>
+<text text-anchor="start" x="2191" y="-2237.8"
font-family="Helvetica,sans-Serif" font-size="14.00">chosen_options</text>
+<text text-anchor="start" x="2299" y="-2237.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2304" y="-2237.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
+<polygon fill="none" stroke="black" points="2186,-2203 2186,-2228 2446,-2228
2446,-2203 2186,-2203"/>
+<text text-anchor="start" x="2191" y="-2212.8"
font-family="Helvetica,sans-Serif" font-size="14.00">created_at</text>
+<text text-anchor="start" x="2264" y="-2212.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2269" y="-2212.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [TIMESTAMP]</text>
+<text text-anchor="start" x="2365" y="-2212.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
+<polygon fill="none" stroke="black" points="2186,-2178 2186,-2203 2446,-2203
2446,-2178 2186,-2178"/>
+<text text-anchor="start" x="2191" y="-2187.8"
font-family="Helvetica,sans-Serif" font-size="14.00">defaults</text>
+<text text-anchor="start" x="2248" y="-2187.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2253" y="-2187.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
+<polygon fill="none" stroke="black" points="2186,-2153 2186,-2178 2446,-2178
2446,-2153 2186,-2153"/>
+<text text-anchor="start" x="2191" y="-2162.8"
font-family="Helvetica,sans-Serif" font-size="14.00">multiple</text>
+<text text-anchor="start" x="2248" y="-2162.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2253" y="-2162.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [BOOLEAN]</text>
+<polygon fill="none" stroke="black" points="2186,-2128 2186,-2153 2446,-2153
2446,-2128 2186,-2128"/>
+<text text-anchor="start" x="2191" y="-2137.8"
font-family="Helvetica,sans-Serif" font-size="14.00">options</text>
+<text text-anchor="start" x="2243" y="-2137.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2248" y="-2137.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
+<text text-anchor="start" x="2299" y="-2137.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
+<polygon fill="none" stroke="black" points="2186,-2103 2186,-2128 2446,-2128
2446,-2103 2186,-2103"/>
+<text text-anchor="start" x="2191" y="-2112.8"
font-family="Helvetica,sans-Serif" font-size="14.00">params</text>
+<text text-anchor="start" x="2244" y="-2112.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2249" y="-2112.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
+<text text-anchor="start" x="2300" y="-2112.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
+<polygon fill="none" stroke="black" points="2186,-2078 2186,-2103 2446,-2103
2446,-2078 2186,-2078"/>
+<text text-anchor="start" x="2191" y="-2087.8"
font-family="Helvetica,sans-Serif" font-size="14.00">params_input</text>
+<text text-anchor="start" x="2287" y="-2087.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2292" y="-2087.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
+<text text-anchor="start" x="2343" y="-2087.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
+<polygon fill="none" stroke="black" points="2186,-2053 2186,-2078 2446,-2078
2446,-2053 2186,-2053"/>
+<text text-anchor="start" x="2191" y="-2062.8"
font-family="Helvetica,sans-Serif" font-size="14.00">responded_at</text>
+<text text-anchor="start" x="2285" y="-2062.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2290" y="-2062.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [TIMESTAMP]</text>
+<polygon fill="none" stroke="black" points="2186,-2028 2186,-2053 2446,-2053
2446,-2028 2186,-2028"/>
+<text text-anchor="start" x="2191" y="-2037.8"
font-family="Helvetica,sans-Serif" font-size="14.00">responded_by</text>
+<text text-anchor="start" x="2289" y="-2037.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2294" y="-2037.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
+<polygon fill="none" stroke="black" points="2186,-2003 2186,-2028 2446,-2028
2446,-2003 2186,-2003"/>
+<text text-anchor="start" x="2191" y="-2012.8"
font-family="Helvetica,sans-Serif" font-size="14.00">subject</text>
+<text text-anchor="start" x="2242" y="-2012.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2247" y="-2012.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> [TEXT]</text>
+<text text-anchor="start" x="2297" y="-2012.8"
font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
</g>
<!-- task_instance--hitl_detail -->
<g id="edge48" class="edge">
<title>task_instance--hitl_detail</title>
-<path fill="none" stroke="#7f7f7f" stroke-dasharray="5,2"
d="M2081.05,-1862.88C2103.53,-1907.23 2127.94,-1950.81 2154,-1991
2164.45,-2007.12 2176.38,-2023.19 2188.91,-2038.71"/>
-<text text-anchor="start" x="2178.91" y="-2027.51" font-family="Times,serif"
font-size="14.00">1</text>
-<text text-anchor="start" x="2081.05" y="-1851.68" font-family="Times,serif"
font-size="14.00">1</text>
+<path fill="none" stroke="#7f7f7f" stroke-dasharray="5,2"
d="M2081.12,-1859.57C2103.71,-1904.92 2128.13,-1949.64 2154,-1991
2161.37,-2002.79 2169.44,-2014.6 2177.92,-2026.25"/>
+<text text-anchor="start" x="2167.92" y="-2015.05" font-family="Times,serif"
font-size="14.00">1</text>
+<text text-anchor="start" x="2081.12" y="-1848.37" font-family="Times,serif"
font-size="14.00">1</text>
</g>
<!-- task_map -->
<g id="node44" class="node">
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 f24688c13e5..aa4f44f212b 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 269f60cd4c3..ec079113922 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 01b8ced149e..95fa0fcda54 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
@@ -10885,6 +10921,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 +10955,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 60d9f99fab9..f4b1e0baae2 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 @@ from airflow.api_fastapi.common.parameters import (
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 (
@@ -209,6 +211,7 @@ def get_hitl_details(
"ti_id",
"subject",
"responded_at",
+ "created_at",
],
model=HITLDetailModel,
to_replace={
@@ -234,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 = (
@@ -265,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/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 cd875711ca4..3105d453e45 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 alembic import op
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 f13c4a6117f..b6bbb2bc402 100644
--- a/airflow-core/src/airflow/models/hitl.py
+++ b/airflow-core/src/airflow/models/hitl.py
@@ -26,6 +26,7 @@ from sqlalchemy.ext.hybrid import hybrid_property
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/queries/common.ts
b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
index 601239c7c72..874ff6b4264 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<ReturnType<typeof TaskInstanceService.getHitlDetails>>;
export type TaskInstanceServiceGetHitlDetailsQueryResult<TData =
TaskInstanceServiceGetHitlDetailsDefaultResponse, TError = unknown> =
UseQueryResult<TData, TError>;
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<unknown>) => [useTaskInstanceServiceGetHitlDetailsKey,
...(queryKey ?? [{ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset,
orderBy, respondedByUserId, respondedByUserName, responseReceived, state,
subjectSearch, taskId, taskIdPattern }])];
+}, queryKey?: Array<unknown>) => [useTaskInstanceServiceGetHitlDetailsKey,
...(queryKey ?? [{ bodySearch, createdAtGt, createdAtGte, createdAtLt,
createdAtLte, dagId, dagIdPattern, dagRunId, limit, offset, orderBy,
respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch,
taskId, taskIdPattern }])];
export type ImportErrorServiceGetImportErrorDefaultResponse =
Awaited<ReturnType<typeof ImportErrorService.getImportError>>;
export type ImportErrorServiceGetImportErrorQueryResult<TData =
ImportErrorServiceGetImportErrorDefaultResponse, TError = unknown> =
UseQueryResult<TData, TError>;
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 a58b538c489..54cf0dca709 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, orde
[...]
/**
* 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 28cbad744a8..66a08f901b3 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, orderB
[...]
/**
* 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 86843abd84c..9c04bac85d4 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 =
<TData = Common.TaskInstanceS
* @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 useTaskInstanceServiceGetHitlDetails = <TData =
Common.TaskInstanceServiceGetHitlDetailsDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = unknown[]>({ bodySearch, dagId,
dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId,
respondedByUserName, responseReceived, state, subjectSearch, taskId,
taskIdPattern }: {
+export const useTaskInstanceServiceGetHitlDetails = <TData =
Common.TaskInstanceServiceGetHitlDetailsDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = 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 =
<TData = Common.TaskInstance
subjectSearch?: string;
taskId?: string;
taskIdPattern?: string;
-}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useQuery<TData, TError>({ 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, res [...]
+}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useQuery<TData, TError>({ 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, crea [...]
/**
* 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 aaed59782df..ba7f0fc93c9 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 = <TData = Common.TaskI
* @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 useTaskInstanceServiceGetHitlDetailsSuspense = <TData =
Common.TaskInstanceServiceGetHitlDetailsDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = unknown[]>({ bodySearch, dagId,
dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId,
respondedByUserName, responseReceived, state, subjectSearch, taskId,
taskIdPattern }: {
+export const useTaskInstanceServiceGetHitlDetailsSuspense = <TData =
Common.TaskInstanceServiceGetHitlDetailsDefaultResponse, TError = unknown,
TQueryKey extends Array<unknown> = 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
= <TData = Common.Task
subjectSearch?: string;
taskId?: string;
taskIdPattern?: string;
-}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ 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, orde [...]
+}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>,
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ 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({ bodySear [...]
/**
* Get Import Error
* Get an import error.
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 1cf2a46b461..32dafcb90fd 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/services.gen.ts
b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
index 4efc34fabda..8b4cb8c21e4 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 636c1b9b20b..256812659a8 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<HITLUser>;
+ created_at: string;
responded_by_user?: HITLUser | null;
responded_at?: string | null;
chosen_options?: Array<(string)> | null;
@@ -2857,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.
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 61f6488d5fc..f8ab8e1c4d9 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 @@ from __future__ import annotations
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 @@ import time_machine
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 @@ ANOTHER_DAG_ID = "another_hitl_dag"
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:])
]
@@ -209,6 +217,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,
@@ -542,6 +551,21 @@ class TestGetHITLDetailsEndpoint:
({"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",
@@ -556,6 +580,9 @@ class TestGetHITLDetailsEndpoint:
"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(
@@ -587,6 +614,7 @@ class TestGetHITLDetailsEndpoint:
"multiple": False,
"params": {"input_1": 1},
"assigned_users": [],
+ "created_at":
DEFAULT_CREATED_AT.isoformat().replace("+00:00", "Z"),
"responded_by_user": None,
"responded_at": None,
"chosen_options": None,
@@ -612,6 +640,7 @@ class TestGetHITLDetailsEndpoint:
# htil key
("subject", itemgetter("subject")),
("responded_at", itemgetter("responded_at")),
+ ("created_at", itemgetter("created_at")),
],
ids=[
"ti_id",
@@ -622,6 +651,7 @@ class TestGetHITLDetailsEndpoint:
"task_instance_operator",
"subject",
"responded_at",
+ "created_at",
],
)
def test_should_respond_200_with_existing_response_and_order_by(
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 d58f29839b6..5ebf2221d92 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 @@ class TestGetDagRuns(TestPublicDagEndpoint):
"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 31da4e13044..739297fd434 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