Skip to content

Commit 234a163

Browse files
committed
feat(hitl): include task_instance detail in hitl detail response
1 parent 12c317b commit 234a163

File tree

5 files changed

+62
-3
lines changed

5 files changed

+62
-3
lines changed

airflow-core/src/airflow/api_fastapi/core_api/datamodels/hitl.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from pydantic import Field, field_validator
2424

2525
from airflow.api_fastapi.core_api.base import BaseModel
26+
from airflow.api_fastapi.core_api.datamodels.task_instances import TaskInstanceResponse
2627
from airflow.sdk import Param
2728

2829

@@ -45,7 +46,7 @@ class HITLDetailResponse(BaseModel):
4546
class HITLDetail(BaseModel):
4647
"""Schema for Human-in-the-loop detail."""
4748

48-
ti_id: str
49+
task_instance: TaskInstanceResponse
4950

5051
# User Request Detail
5152
options: list[str]

airflow-core/src/airflow/api_fastapi/core_api/routes/public/hitl.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import structlog
2020
from fastapi import Depends, HTTPException, status
2121
from sqlalchemy import select
22+
from sqlalchemy.orm import joinedload
2223

2324
from airflow.api_fastapi.auth.managers.models.resource_details import DagAccessEntity
2425
from airflow.api_fastapi.common.db.common import SessionDep, paginated_select
@@ -132,7 +133,11 @@ def _get_hitl_detail(
132133
)
133134

134135
ti_id_str = str(task_instance.id)
135-
hitl_detail_model = session.scalar(select(HITLDetailModel).where(HITLDetailModel.ti_id == ti_id_str))
136+
hitl_detail_model = session.scalar(
137+
select(HITLDetailModel)
138+
.where(HITLDetailModel.ti_id == ti_id_str)
139+
.options(joinedload(HITLDetailModel.task_instance))
140+
)
136141
if not hitl_detail_model:
137142
log.error("Human-in-the-loop detail not found")
138143
raise HTTPException(

airflow-core/src/airflow/models/hitl.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from sqlalchemy import Boolean, Column, ForeignKeyConstraint, String, Text
2121
from sqlalchemy.dialects import postgresql
2222
from sqlalchemy.ext.hybrid import hybrid_property
23+
from sqlalchemy.orm import relationship
2324

2425
from airflow.models.base import Base
2526
from airflow.settings import json
@@ -53,6 +54,11 @@ class HITLDetail(Base):
5354
default=None,
5455
)
5556
params_input = Column(sqlalchemy_jsonfield.JSONField(json=json), nullable=False, default={})
57+
task_instance = relationship(
58+
"TaskInstance",
59+
lazy="joined",
60+
back_populates="hitl_detail",
61+
)
5662

5763
__table_args__ = (
5864
ForeignKeyConstraint(

airflow-core/src/airflow/models/taskinstance.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,8 @@ class TaskInstance(Base, LoggingMixin):
555555
triggerer_job = association_proxy("trigger", "triggerer_job")
556556
dag_run = relationship("DagRun", back_populates="task_instances", lazy="joined", innerjoin=True)
557557
rendered_task_instance_fields = relationship("RenderedTaskInstanceFields", lazy="noload", uselist=False)
558+
hitl_detail = relationship("HITLDetail", lazy="noload", uselist=False)
559+
558560
run_after = association_proxy("dag_run", "run_after")
559561
logical_date = association_proxy("dag_run", "logical_date")
560562
task_instance_note = relationship(

airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_hitl.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
# under the License.
1717
from __future__ import annotations
1818

19+
from unittest import mock
20+
1921
import pytest
2022
from sqlalchemy.orm import Session
2123

@@ -111,8 +113,51 @@ def expected_sample_hitl_detail_dict(sample_ti: TaskInstance) -> dict[str, Any]:
111113
"chosen_options": None,
112114
"response_received": False,
113115
"subject": "This is subject",
114-
"ti_id": sample_ti.id,
115116
"user_id": None,
117+
"task_instance": {
118+
"dag_display_name": "dag",
119+
"dag_id": "dag",
120+
"dag_run_id": "test",
121+
"dag_version": {
122+
"bundle_name": "dag_maker",
123+
"bundle_url": None,
124+
"bundle_version": None,
125+
"created_at": mock.ANY,
126+
"dag_display_name": "dag",
127+
"dag_id": "dag",
128+
"id": mock.ANY,
129+
"version_number": 1,
130+
},
131+
"duration": None,
132+
"end_date": None,
133+
"executor": None,
134+
"executor_config": "{}",
135+
"hostname": "",
136+
"id": sample_ti.id,
137+
"logical_date": mock.ANY,
138+
"map_index": -1,
139+
"max_tries": 0,
140+
"note": None,
141+
"operator": "EmptyOperator",
142+
"pid": None,
143+
"pool": "default_pool",
144+
"pool_slots": 1,
145+
"priority_weight": 1,
146+
"queue": "default",
147+
"queued_when": None,
148+
"rendered_fields": {},
149+
"rendered_map_index": None,
150+
"run_after": mock.ANY,
151+
"scheduled_when": None,
152+
"start_date": None,
153+
"state": None,
154+
"task_display_name": "op1",
155+
"task_id": "op1",
156+
"trigger": None,
157+
"triggerer_job": None,
158+
"try_number": 0,
159+
"unixname": "root",
160+
},
116161
}
117162

118163

0 commit comments

Comments
 (0)