You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by ka...@apache.org on 2025/09/15 00:36:23 UTC
(airflow) branch v3-1-test updated (38ca6d3409e -> 900b079d2f9)
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a change to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
from 38ca6d3409e Upgrade important versions of dependencies (#55646)
new e20ee338072 Use FilterBar for Asset Event filters (#55522)
new f87674b12d8 Update query client strategy (#55528)
new 78b28f8c0b0 Fix gantt chart rendering issues (#55554)
new 189272713da Mark React Apps as Experimental for Airflow 3.1 (#55478)
new 1958bcc05d8 Be a little clearer when task likely OOM error (#55602)
new 792cc4fb1ec fix(ui): Connection Form Extras not inferring the correct type (#55492)
new 7fef4a28399 fix(hitl): make the user model in HITLDetail consistent with airflow user model (#55463)
new 835a8636b97 Bump axios (#55552)
new b6fec33bf5c Bump axios from 1.11.0 to 1.12.0 in /airflow-core/src/airflow/ui (#55551)
new 44724c2af49 Remove config loader from react tests (#55541)
new bb09da4770d Add autorefresh to Required Actions button (#55543)
new ce5dc197fc7 Add hitl required action count (#55546)
new 4b76188ee7d feat(hitl): get rid off "Fallback to defaults" in HITL (#55536)
new 665c69397fd Remove FAB provider from expected providers for PROD image 3.13 verification (#55523)
new 58735c44ba5 Unify datetime format in the UI (#55572)
new 73f25ace72d Prevent HITL from emitting reqeust for settled dag and tasks (#55575)
new dd8690c4fea Fix Grid cache invalidation for multi-run task operations (#55504)
new 6a440b56c1f refactor(hitl): rename response_at to responded_at (#55535)
new 28f931c7f59 Bump React and React-DOM (#55598)
new b6bfb313bf9 Include mypy upgrades to upgrade-important-versions prek hook (#55597)
new caa07e2c12a Separate GoLang precommit after prek supports monorepo setup (#54615)
new 900b079d2f9 Update 3.1.0 release notes based on latest cherry-picks
The 22 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.github/actions/install-prek/action.yml | 2 +-
.github/workflows/basic-tests.yml | 2 +
.github/workflows/ci-amd.yml | 2 +-
.github/workflows/prod-image-build.yml | 4 +
.pre-commit-config.yaml | 45 +-
RELEASE_NOTES.rst | 20 +-
.../docs/administration-and-deployment/plugins.rst | 3 +
airflow-core/docs/howto/custom-view-plugin.rst | 7 +
airflow-core/docs/img/airflow_erd.sha256 | 2 +-
airflow-core/docs/img/airflow_erd.svg | 120 +++---
airflow-core/docs/troubleshooting.rst | 22 +-
airflow-core/docs/tutorial/hitl.rst | 3 +
.../auth/managers/simple/ui/package-lock.json | 8 +-
.../auth/managers/simple/ui/package.json | 2 +-
.../auth/managers/simple/ui/pnpm-lock.yaml | 110 ++---
.../src/airflow/api_fastapi/common/parameters.py | 11 +-
.../api_fastapi/core_api/datamodels/hitl.py | 19 +-
.../api_fastapi/core_api/openapi/_private_ui.yaml | 40 +-
.../core_api/openapi/v2-rest-api-generated.yaml | 65 +--
.../api_fastapi/core_api/routes/public/hitl.py | 20 +-
.../airflow/api_fastapi/core_api/routes/ui/dags.py | 2 +-
.../api_fastapi/execution_api/datamodels/hitl.py | 28 +-
.../api_fastapi/execution_api/routes/hitl.py | 9 +-
.../0076_3_1_0_add_human_in_the_loop_response.py | 7 +-
airflow-core/src/airflow/models/hitl.py | 102 ++++-
.../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 | 73 ++--
.../ui/openapi-gen/requests/services.gen.ts | 8 +-
.../airflow/ui/openapi-gen/requests/types.gen.ts | 24 +-
airflow-core/src/airflow/ui/package.json | 10 +-
airflow-core/src/airflow/ui/pnpm-lock.yaml | 468 +++++++++++----------
.../ui/src/components/Assets/AssetEventsFilter.tsx | 107 ++---
.../airflow/ui/src/components/DateTimeInput.tsx | 3 +-
.../airflow/ui/src/components/DurationChart.tsx | 3 +-
.../src/components/FlexibleForm/FieldSelector.tsx | 14 +-
.../ui/src/components/NeedsReviewButton.tsx | 23 +-
.../src/airflow/ui/src/components/Time.test.tsx | 9 +-
.../src/airflow/ui/src/components/Time.tsx | 9 +-
.../src/components/TriggerDag/TriggerDAGForm.tsx | 3 +-
.../src/airflow/ui/src/constants/filterConfigs.tsx | 10 +
.../ui/src/context/colorMode/ColorModeProvider.tsx | 3 +-
.../airflow/ui/src/hooks/useRequiredActionTabs.ts | 127 ++++++
.../airflow/ui/src/layouts/Details/Gantt/Gantt.tsx | 10 +-
.../airflow/ui/src/layouts/Details/Gantt/utils.ts | 22 +-
.../ui/src/layouts/Nav/TimezoneSelector.tsx | 3 +-
airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx | 45 +-
.../airflow/ui/src/pages/Dag/Overview/Overview.tsx | 8 +-
.../pages/HITLTaskInstances/HITLResponseForm.tsx | 3 +-
.../pages/HITLTaskInstances/HITLTaskInstances.tsx | 5 +-
airflow-core/src/airflow/ui/src/pages/Run/Run.tsx | 26 +-
.../ui/src/pages/Task/Overview/Overview.tsx | 7 +-
.../src/airflow/ui/src/pages/Task/Task.tsx | 21 +-
.../ui/src/pages/TaskInstance/TaskInstance.tsx | 44 +-
.../ui/src/queries/useClearTaskInstances.ts | 9 +-
.../airflow/ui/src/queries/usePatchTaskInstance.ts | 19 +-
airflow-core/src/airflow/ui/src/queryClient.ts | 6 +-
.../src/airflow/ui/src/utils/AppWrapper.tsx | 8 +-
.../src/airflow/ui/src/utils/datetimeUtils.ts | 5 +-
.../src/airflow/ui/src/utils/useFiltersHandler.ts | 2 +
.../core_api/routes/public/test_hitl.py | 44 +-
.../api_fastapi/core_api/routes/ui/test_dags.py | 5 +-
.../execution_api/versions/head/test_hitl.py | 25 +-
.../src/airflowctl/api/datamodels/generated.py | 29 +-
contributing-docs/08_static_code_checks.rst | 4 +-
{.github => dev}/.pre-commit-config.yaml | 0
.../react_plugin_template/package.json | 8 +-
.../src/context/colorMode/ColorModeProvider.tsx | 3 +-
docker-tests/tests/docker_tests/test_prod_image.py | 27 +-
go-sdk/.pre-commit-config.yaml | 73 ++++
.../standard/example_dags/example_hitl_operator.py | 1 +
.../airflow/providers/standard/operators/hitl.py | 9 +-
.../airflow/providers/standard/triggers/hitl.py | 42 +-
.../tests/unit/standard/operators/test_hitl.py | 83 +++-
.../tests/unit/standard/triggers/test_hitl.py | 37 +-
reproducible_build.yaml | 4 +-
scripts/ci/prek/upgrade_important_versions.py | 269 +++++++-----
task-sdk/src/airflow/sdk/api/client.py | 5 +-
.../src/airflow/sdk/api/datamodels/_generated.py | 54 ++-
task-sdk/src/airflow/sdk/execution_time/hitl.py | 16 +-
.../src/airflow/sdk/execution_time/supervisor.py | 7 +-
task-sdk/tests/task_sdk/api/test_client.py | 23 +-
.../tests/task_sdk/execution_time/test_hitl.py | 27 +-
.../task_sdk/execution_time/test_supervisor.py | 20 +-
87 files changed, 1575 insertions(+), 1088 deletions(-)
create mode 100644 airflow-core/src/airflow/ui/src/hooks/useRequiredActionTabs.ts
rename {.github => dev}/.pre-commit-config.yaml (100%)
create mode 100644 go-sdk/.pre-commit-config.yaml
(airflow) 20/22: Include mypy upgrades to upgrade-important-versions prek hook (#55597)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit b6bfb313bf9583fb31daf95210f58bb9ba4967e6
Author: GPK <go...@gmail.com>
AuthorDate: Fri Sep 12 23:06:36 2025 +0100
Include mypy upgrades to upgrade-important-versions prek hook (#55597)
(cherry picked from commit fe2ddb090c7921552599531ed89bb9df05cbca55)
---
.github/workflows/basic-tests.yml | 2 ++
scripts/ci/prek/upgrade_important_versions.py | 11 +++++++++++
2 files changed, 13 insertions(+)
diff --git a/.github/workflows/basic-tests.yml b/.github/workflows/basic-tests.yml
index b729c0efabd..48978c1351b 100644
--- a/.github/workflows/basic-tests.yml
+++ b/.github/workflows/basic-tests.yml
@@ -319,6 +319,7 @@ jobs:
UPGRADE_GITPYTHON: "false"
UPGRADE_RICH: "false"
UPGRADE_RUFF: "false"
+ UPGRADE_MYPY: "false"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: "Run automated upgrade for important versions minus uv(failing if needed)"
run: >
@@ -338,6 +339,7 @@ jobs:
UPGRADE_GITPYTHON: "true"
UPGRADE_RICH: "true"
UPGRADE_RUFF: "true"
+ UPGRADE_MYPY: "true"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
test-airflow-release-commands:
diff --git a/scripts/ci/prek/upgrade_important_versions.py b/scripts/ci/prek/upgrade_important_versions.py
index 9beecc67e76..75dc99e1be2 100755
--- a/scripts/ci/prek/upgrade_important_versions.py
+++ b/scripts/ci/prek/upgrade_important_versions.py
@@ -223,6 +223,7 @@ UPGRADE_PYYAML: bool = os.environ.get("UPGRADE_PYYAML", "true").lower() == "true
UPGRADE_GITPYTHON: bool = os.environ.get("UPGRADE_GITPYTHON", "true").lower() == "true"
UPGRADE_RICH: bool = os.environ.get("UPGRADE_RICH", "true").lower() == "true"
UPGRADE_RUFF: bool = os.environ.get("UPGRADE_RUFF", "true").lower() == "true"
+UPGRADE_MYPY: bool = os.environ.get("UPGRADE_MYPY", "true").lower() == "true"
ALL_PYTHON_MAJOR_MINOR_VERSIONS = ["3.9", "3.10", "3.11", "3.12", "3.13"]
DEFAULT_PROD_IMAGE_PYTHON_VERSION = "3.12"
@@ -269,6 +270,7 @@ if __name__ == "__main__":
rich_version = get_latest_pypi_version("rich")
ruff_version = get_latest_pypi_version("ruff")
node_lts_version = get_latest_lts_node_version()
+ mypy_version = get_latest_pypi_version("mypy")
for file, keep_length in FILES_TO_UPDATE:
console.print(f"[bright_blue]Updating {file}")
file_content = file.read_text()
@@ -396,6 +398,15 @@ if __name__ == "__main__":
f"ruff>={ruff_version}",
new_content,
)
+ if UPGRADE_MYPY:
+ console.print(f"[bright_blue]Latest mypy version: {mypy_version}")
+
+ new_content = re.sub(
+ r"(mypy==)([0-9.]+)",
+ f"mypy=={mypy_version}",
+ new_content,
+ )
+
if new_content != file_content:
file.write_text(new_content)
console.print(f"[bright_blue]Updated {file}")
(airflow) 19/22: Bump React and React-DOM (#55598)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 28f931c7f59da94c98373dde83f4776503271171
Author: Jens Scheffler <95...@users.noreply.github.com>
AuthorDate: Sat Sep 13 09:13:33 2025 +0200
Bump React and React-DOM (#55598)
(cherry picked from commit d4cdee99b6567ce991a813669892e8c1db33075d)
---
airflow-core/src/airflow/ui/package.json | 8 +-
airflow-core/src/airflow/ui/pnpm-lock.yaml | 441 +++++++++++----------
.../ui/src/context/colorMode/ColorModeProvider.tsx | 3 +-
.../react_plugin_template/package.json | 8 +-
.../src/context/colorMode/ColorModeProvider.tsx | 3 +-
5 files changed, 242 insertions(+), 221 deletions(-)
diff --git a/airflow-core/src/airflow/ui/package.json b/airflow-core/src/airflow/ui/package.json
index 058c1a5a1de..70ad713ba27 100644
--- a/airflow-core/src/airflow/ui/package.json
+++ b/airflow-core/src/airflow/ui/package.json
@@ -41,11 +41,11 @@
"i18next": "^25.1.2",
"i18next-browser-languagedetector": "^8.1.0",
"i18next-http-backend": "^3.0.2",
- "next-themes": "^0.3.0",
+ "next-themes": "^0.4.6",
"node-sql-parser": "^5.3.10",
- "react": "^18.3.1",
+ "react": "^19.1.1",
"react-chartjs-2": "^5.3.0",
- "react-dom": "^18.3.1",
+ "react-dom": "^19.1.1",
"react-hook-form": "^7.56.1",
"react-hotkeys-hook": "^4.6.1",
"react-i18next": "^15.5.1",
@@ -85,7 +85,7 @@
"eslint-plugin-perfectionist": "^4.12.3",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-react": "^7.37.5",
- "eslint-plugin-react-hooks": "^4.6.2",
+ "eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"eslint-plugin-unicorn": "^55.0.0",
"globals": "^15.15.0",
diff --git a/airflow-core/src/airflow/ui/pnpm-lock.yaml b/airflow-core/src/airflow/ui/pnpm-lock.yaml
index 3f0baca129e..ae88dad9792 100644
--- a/airflow-core/src/airflow/ui/pnpm-lock.yaml
+++ b/airflow-core/src/airflow/ui/pnpm-lock.yaml
@@ -13,22 +13,22 @@ importers:
version: 2.3.4
'@chakra-ui/react':
specifier: ^3.20.0
- version: 3.20.0(@emotion/react@11.14.0(@types/react@18.3.19)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 3.20.0(@emotion/react@11.14.0(@types/react@18.3.19)(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@codemirror/lang-json':
specifier: ^6.0.1
version: 6.0.1
'@emotion/react':
specifier: ^11.14.0
- version: 11.14.0(@types/react@18.3.19)(react@18.3.1)
+ version: 11.14.0(@types/react@18.3.19)(react@19.1.1)
'@tanstack/react-query':
specifier: ^5.75.1
- version: 5.75.4(react@18.3.1)
+ version: 5.75.4(react@19.1.1)
'@tanstack/react-table':
specifier: ^8.21.3
- version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 8.21.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tanstack/react-virtual':
specifier: ^3.13.8
- version: 3.13.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 3.13.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@types/debounce-promise':
specifier: ^3.1.9
version: 3.1.9
@@ -37,22 +37,22 @@ importers:
version: 4.23.12(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)
'@uiw/react-codemirror':
specifier: ^4.23.12
- version: 4.23.12(@babel/runtime@7.26.10)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.6)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.4)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 4.23.12(@babel/runtime@7.28.4)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.6)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.4)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@visx/group':
specifier: ^3.12.0
- version: 3.12.0(react@18.3.1)
+ version: 3.12.0(react@19.1.1)
'@visx/shape':
specifier: ^3.12.0
- version: 3.12.0(react@18.3.1)
+ version: 3.12.0(react@19.1.1)
'@xyflow/react':
specifier: ^12.4.4
- version: 12.4.4(@types/react@18.3.19)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 12.4.4(@types/react@18.3.19)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
axios:
specifier: ^1.12.0
version: 1.12.0
chakra-react-select:
specifier: 6.1.0
- version: 6.1.0(@chakra-ui/react@3.20.0(@emotion/react@11.14.0(@types/react@18.3.19)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.19)(next-themes@0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 6.1.0(@chakra-ui/react@3.20.0(@emotion/react@11.14.0(@types/react@18.3.19)(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@18.3.19)(next-themes@0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
chart.js:
specifier: ^4.4.9
version: 4.4.9
@@ -84,65 +84,65 @@ importers:
specifier: ^3.0.2
version: 3.0.2
next-themes:
- specifier: ^0.3.0
- version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ specifier: ^0.4.6
+ version: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
node-sql-parser:
specifier: ^5.3.10
version: 5.3.10
react:
- specifier: ^18.3.1
- version: 18.3.1
+ specifier: ^19.1.1
+ version: 19.1.1
react-chartjs-2:
specifier: ^5.3.0
- version: 5.3.0(chart.js@4.4.9)(react@18.3.1)
+ version: 5.3.0(chart.js@4.4.9)(react@19.1.1)
react-dom:
- specifier: ^18.3.1
- version: 18.3.1(react@18.3.1)
+ specifier: ^19.1.1
+ version: 19.1.1(react@19.1.1)
react-hook-form:
specifier: ^7.56.1
- version: 7.56.2(react@18.3.1)
+ version: 7.56.2(react@19.1.1)
react-hotkeys-hook:
specifier: ^4.6.1
- version: 4.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 4.6.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react-i18next:
specifier: ^15.5.1
- version: 15.5.1(i18next@25.1.2(typescript@5.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)
+ version: 15.5.1(i18next@25.1.2(typescript@5.8.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.8.3)
react-icons:
specifier: ^5.5.0
- version: 5.5.0(react@18.3.1)
+ version: 5.5.0(react@19.1.1)
react-innertext:
specifier: ^1.1.5
- version: 1.1.5(@types/react@18.3.19)(react@18.3.1)
+ version: 1.1.5(@types/react@18.3.19)(react@19.1.1)
react-json-view:
specifier: ^1.21.3
- version: 1.21.3(@types/react@18.3.19)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 1.21.3(@types/react@18.3.19)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react-markdown:
specifier: ^9.1.0
- version: 9.1.0(@types/react@18.3.19)(react@18.3.1)
+ version: 9.1.0(@types/react@18.3.19)(react@19.1.1)
react-resizable-panels:
specifier: ^2.1.7
- version: 2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 2.1.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react-router-dom:
specifier: ^6.30.0
- version: 6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 6.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react-syntax-highlighter:
specifier: ^15.6.1
- version: 15.6.1(react@18.3.1)
+ version: 15.6.1(react@19.1.1)
remark-gfm:
specifier: ^4.0.1
version: 4.0.1
use-debounce:
specifier: ^10.0.4
- version: 10.0.4(react@18.3.1)
+ version: 10.0.4(react@19.1.1)
usehooks-ts:
specifier: ^3.1.1
- version: 3.1.1(react@18.3.1)
+ version: 3.1.1(react@19.1.1)
yaml:
specifier: ^2.6.1
version: 2.8.0
zustand:
specifier: ^5.0.4
- version: 5.0.4(@types/react@18.3.19)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1))
+ version: 5.0.4(@types/react@18.3.19)(react@19.1.1)(use-sync-external-store@1.4.0(react@19.1.1))
devDependencies:
'@7nohe/openapi-react-query-codegen':
specifier: ^1.6.2
@@ -164,7 +164,7 @@ importers:
version: 6.6.3
'@testing-library/react':
specifier: ^16.3.0
- version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.19))(@types/react@18.3.19)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.19))(@types/react@18.3.19)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@trivago/prettier-plugin-sort-imports':
specifier: ^4.3.0
version: 4.3.0(prettier@3.5.3)
@@ -211,8 +211,8 @@ importers:
specifier: ^7.37.5
version: 7.37.5(eslint@9.26.0(jiti@1.21.7))
eslint-plugin-react-hooks:
- specifier: ^4.6.2
- version: 4.6.2(eslint@9.26.0(jiti@1.21.7))
+ specifier: ^5.2.0
+ version: 5.2.0(eslint@9.26.0(jiti@1.21.7))
eslint-plugin-react-refresh:
specifier: ^0.4.20
version: 0.4.20(eslint@9.26.0(jiti@1.21.7))
@@ -342,6 +342,10 @@ packages:
resolution: {integrity: sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==}
engines: {node: '>=6.9.0'}
+ '@babel/runtime@7.28.4':
+ resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
+ engines: {node: '>=6.9.0'}
+
'@babel/template@7.26.9':
resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==}
engines: {node: '>=6.9.0'}
@@ -1165,8 +1169,8 @@ packages:
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
- '@types/lodash@4.17.16':
- resolution: {integrity: sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==}
+ '@types/lodash@4.17.20':
+ resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==}
'@types/mdast@4.0.4':
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
@@ -1749,6 +1753,11 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
+ acorn@8.15.0:
+ resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
@@ -2264,8 +2273,8 @@ packages:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
- destr@2.0.3:
- resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==}
+ destr@2.0.5:
+ resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
@@ -2283,8 +2292,8 @@ packages:
dom-helpers@5.2.1:
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
- dotenv@16.4.7:
- resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==}
+ dotenv@16.6.1:
+ resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
engines: {node: '>=12'}
dunder-proto@1.0.1:
@@ -2437,11 +2446,11 @@ packages:
eslint-config-prettier:
optional: true
- eslint-plugin-react-hooks@4.6.2:
- resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==}
+ eslint-plugin-react-hooks@5.2.0:
+ resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==}
engines: {node: '>=10'}
peerDependencies:
- eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
eslint-plugin-react-refresh@0.4.20:
resolution: {integrity: sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==}
@@ -3388,8 +3397,8 @@ packages:
engines: {node: '>=10'}
hasBin: true
- mlly@1.7.4:
- resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==}
+ mlly@1.8.0:
+ resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -3427,14 +3436,14 @@ packages:
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
- next-themes@0.3.0:
- resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==}
+ next-themes@0.4.6:
+ resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
peerDependencies:
- react: ^16.8 || ^17 || ^18
- react-dom: ^16.8 || ^17 || ^18
+ react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
- node-fetch-native@1.6.6:
- resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==}
+ node-fetch-native@1.6.7:
+ resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
@@ -3730,10 +3739,10 @@ packages:
chart.js: ^4.1.1
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- react-dom@18.3.1:
- resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
+ react-dom@19.1.1:
+ resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==}
peerDependencies:
- react: ^18.3.1
+ react: ^19.1.1
react-hook-form@7.56.2:
resolution: {integrity: sha512-vpfuHuQMF/L6GpuQ4c3ZDo+pRYxIi40gQqsCmmfUBwm+oqvBhKhwghCuj2o00YCgSfU6bR9KC/xnQGWm3Gr08A==}
@@ -3825,8 +3834,8 @@ packages:
peerDependencies:
react: '>= 0.14.0'
- react-textarea-autosize@8.5.8:
- resolution: {integrity: sha512-iUiIj70JefrTuSJ4LbVFiSqWiHHss5L63L717bqaWHMgkm9sz6eEvro4vZ3uQfGJbevzwT6rHOszHKA8RkhRMg==}
+ react-textarea-autosize@8.5.9:
+ resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==}
engines: {node: '>=10'}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -3837,8 +3846,8 @@ packages:
react: '>=16.6.0'
react-dom: '>=16.6.0'
- react@18.3.1:
- resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+ react@19.1.1:
+ resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
engines: {node: '>=0.10.0'}
read-pkg-up@7.0.1:
@@ -3952,8 +3961,8 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
- scheduler@0.23.2:
- resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+ scheduler@0.26.0:
+ resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
semver@5.7.2:
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
@@ -4267,12 +4276,12 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
- ua-parser-js@1.0.40:
- resolution: {integrity: sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==}
+ ua-parser-js@1.0.41:
+ resolution: {integrity: sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==}
hasBin: true
- ufo@1.5.4:
- resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
+ ufo@1.6.1:
+ resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
uglify-js@3.19.3:
resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==}
@@ -4354,6 +4363,15 @@ packages:
'@types/react':
optional: true
+ use-isomorphic-layout-effect@1.2.1:
+ resolution: {integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
use-latest@1.3.0:
resolution: {integrity: sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==}
peerDependencies:
@@ -4633,7 +4651,7 @@ snapshots:
'@types/json-schema': 7.0.15
js-yaml: 4.1.0
- '@ark-ui/react@5.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@ark-ui/react@5.12.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@internationalized/date': 3.8.1
'@zag-js/accordion': 1.15.0
@@ -4674,7 +4692,7 @@ snapshots:
'@zag-js/qr-code': 1.15.0
'@zag-js/radio-group': 1.15.0
'@zag-js/rating-group': 1.15.0
- '@zag-js/react': 1.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@zag-js/react': 1.15.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@zag-js/select': 1.15.0
'@zag-js/signature-pad': 1.15.0
'@zag-js/slider': 1.15.0
@@ -4693,8 +4711,8 @@ snapshots:
'@zag-js/tree-view': 1.15.0
'@zag-js/types': 1.15.0
'@zag-js/utils': 1.15.0
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
'@babel/code-frame@7.26.2':
dependencies:
@@ -4760,6 +4778,8 @@ snapshots:
dependencies:
regenerator-runtime: 0.14.1
+ '@babel/runtime@7.28.4': {}
+
'@babel/template@7.26.9':
dependencies:
'@babel/code-frame': 7.26.2
@@ -4820,19 +4840,19 @@ snapshots:
'@chakra-ui/anatomy@2.3.4': {}
- '@chakra-ui/react@3.20.0(@emotion/react@11.14.0(@types/react@18.3.19)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@chakra-ui/react@3.20.0(@emotion/react@11.14.0(@types/react@18.3.19)(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
- '@ark-ui/react': 5.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@ark-ui/react': 5.12.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@emotion/is-prop-valid': 1.3.1
- '@emotion/react': 11.14.0(@types/react@18.3.19)(react@18.3.1)
+ '@emotion/react': 11.14.0(@types/react@18.3.19)(react@19.1.1)
'@emotion/serialize': 1.3.3
- '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
+ '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.1)
'@emotion/utils': 1.4.2
'@pandacss/is-valid-prop': 0.53.6
csstype: 3.1.3
fast-safe-stringify: 2.1.1
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
'@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)(@lezer/common@1.2.3)':
dependencies:
@@ -4923,17 +4943,17 @@ snapshots:
'@emotion/memoize@0.9.0': {}
- '@emotion/react@11.14.0(@types/react@18.3.19)(react@18.3.1)':
+ '@emotion/react@11.14.0(@types/react@18.3.19)(react@19.1.1)':
dependencies:
'@babel/runtime': 7.26.10
'@emotion/babel-plugin': 11.13.5
'@emotion/cache': 11.14.0
'@emotion/serialize': 1.3.3
- '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
+ '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.1)
'@emotion/utils': 1.4.2
'@emotion/weak-memoize': 0.4.0
hoist-non-react-statics: 3.3.2
- react: 18.3.1
+ react: 19.1.1
optionalDependencies:
'@types/react': 18.3.19
transitivePeerDependencies:
@@ -4951,9 +4971,9 @@ snapshots:
'@emotion/unitless@0.10.0': {}
- '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.3.1)':
+ '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.1.1)':
dependencies:
- react: 18.3.1
+ react: 19.1.1
'@emotion/utils@1.4.2': {}
@@ -5407,22 +5427,22 @@ snapshots:
'@tanstack/query-core@5.75.4': {}
- '@tanstack/react-query@5.75.4(react@18.3.1)':
+ '@tanstack/react-query@5.75.4(react@19.1.1)':
dependencies:
'@tanstack/query-core': 5.75.4
- react: 18.3.1
+ react: 19.1.1
- '@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@tanstack/react-table@8.21.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@tanstack/table-core': 8.21.3
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
- '@tanstack/react-virtual@3.13.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@tanstack/react-virtual@3.13.8(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@tanstack/virtual-core': 3.13.8
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
'@tanstack/table-core@8.21.3': {}
@@ -5431,7 +5451,7 @@ snapshots:
'@testing-library/dom@10.4.0':
dependencies:
'@babel/code-frame': 7.27.1
- '@babel/runtime': 7.26.10
+ '@babel/runtime': 7.28.4
'@types/aria-query': 5.0.4
aria-query: 5.3.0
chalk: 4.1.2
@@ -5449,12 +5469,12 @@ snapshots:
lodash: 4.17.21
redent: 3.0.0
- '@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.19))(@types/react@18.3.19)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@18.3.5(@types/react@18.3.19))(@types/react@18.3.19)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@babel/runtime': 7.26.10
'@testing-library/dom': 10.4.0
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
optionalDependencies:
'@types/react': 18.3.19
'@types/react-dom': 18.3.5(@types/react@18.3.19)
@@ -5559,7 +5579,7 @@ snapshots:
'@types/json-schema@7.0.15': {}
- '@types/lodash@4.17.16': {}
+ '@types/lodash@4.17.20': {}
'@types/mdast@4.0.4':
dependencies:
@@ -6094,17 +6114,17 @@ snapshots:
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.4
- '@uiw/react-codemirror@4.23.12(@babel/runtime@7.26.10)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.6)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.4)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@uiw/react-codemirror@4.23.12(@babel/runtime@7.28.4)(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)(@lezer/common@1.2.3))(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.6)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.4)(codemirror@6.0.1(@lezer/common@1.2.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
- '@babel/runtime': 7.26.10
+ '@babel/runtime': 7.28.4
'@codemirror/commands': 6.8.1
'@codemirror/state': 6.5.2
'@codemirror/theme-one-dark': 6.1.2
'@codemirror/view': 6.36.4
'@uiw/codemirror-extensions-basic-setup': 4.23.12(@codemirror/autocomplete@6.18.2(@codemirror/language@6.11.0)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)(@lezer/common@1.2.3))(@codemirror/commands@6.8.1)(@codemirror/language@6.11.0)(@codemirror/lint@6.8.2)(@codemirror/search@6.5.6)(@codemirror/state@6.5.2)(@codemirror/view@6.36.4)
codemirror: 6.0.1(@lezer/common@1.2.3)
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
transitivePeerDependencies:
- '@codemirror/autocomplete'
- '@codemirror/language'
@@ -6118,32 +6138,32 @@ snapshots:
'@types/d3-shape': 1.3.12
d3-shape: 1.3.7
- '@visx/group@3.12.0(react@18.3.1)':
+ '@visx/group@3.12.0(react@19.1.1)':
dependencies:
'@types/react': 18.3.19
classnames: 2.5.1
prop-types: 15.8.1
- react: 18.3.1
+ react: 19.1.1
'@visx/scale@3.12.0':
dependencies:
'@visx/vendor': 3.12.0
- '@visx/shape@3.12.0(react@18.3.1)':
+ '@visx/shape@3.12.0(react@19.1.1)':
dependencies:
'@types/d3-path': 1.0.11
'@types/d3-shape': 1.3.12
- '@types/lodash': 4.17.16
+ '@types/lodash': 4.17.20
'@types/react': 18.3.19
'@visx/curve': 3.12.0
- '@visx/group': 3.12.0(react@18.3.1)
+ '@visx/group': 3.12.0(react@19.1.1)
'@visx/scale': 3.12.0
classnames: 2.5.1
d3-path: 1.0.9
d3-shape: 1.3.7
lodash: 4.17.21
prop-types: 15.8.1
- react: 18.3.1
+ react: 19.1.1
'@visx/vendor@3.12.0':
dependencies:
@@ -6233,13 +6253,13 @@ snapshots:
loupe: 3.1.3
tinyrainbow: 1.2.0
- '@xyflow/react@12.4.4(@types/react@18.3.19)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@xyflow/react@12.4.4(@types/react@18.3.19)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@xyflow/system': 0.0.52
classcat: 5.0.5
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- zustand: 4.5.6(@types/react@18.3.19)(react@18.3.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ zustand: 4.5.6(@types/react@18.3.19)(react@19.1.1)
transitivePeerDependencies:
- '@types/react'
- immer
@@ -6576,14 +6596,14 @@ snapshots:
'@zag-js/types': 1.15.0
'@zag-js/utils': 1.15.0
- '@zag-js/react@1.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ '@zag-js/react@1.15.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@zag-js/core': 1.15.0
'@zag-js/store': 1.15.0
'@zag-js/types': 1.15.0
'@zag-js/utils': 1.15.0
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
'@zag-js/rect-utils@1.15.0': {}
@@ -6764,6 +6784,8 @@ snapshots:
acorn@8.14.1: {}
+ acorn@8.15.0: {}
+
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@@ -6949,10 +6971,10 @@ snapshots:
chokidar: 3.6.0
confbox: 0.1.8
defu: 6.1.4
- dotenv: 16.4.7
+ dotenv: 16.6.1
giget: 1.2.5
jiti: 1.21.7
- mlly: 1.7.4
+ mlly: 1.8.0
ohash: 1.1.6
pathe: 1.1.2
perfect-debounce: 1.0.0
@@ -6996,12 +7018,12 @@ snapshots:
loupe: 3.1.3
pathval: 2.0.0
- chakra-react-select@6.1.0(@chakra-ui/react@3.20.0(@emotion/react@11.14.0(@types/react@18.3.19)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.19)(next-themes@0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ chakra-react-select@6.1.0(@chakra-ui/react@3.20.0(@emotion/react@11.14.0(@types/react@18.3.19)(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@types/react@18.3.19)(next-themes@0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
- '@chakra-ui/react': 3.20.0(@emotion/react@11.14.0(@types/react@18.3.19)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- next-themes: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
- react-select: 5.10.1(@types/react@18.3.19)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@chakra-ui/react': 3.20.0(@emotion/react@11.14.0(@types/react@18.3.19)(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ next-themes: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ react: 19.1.1
+ react-select: 5.10.1(@types/react@18.3.19)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
transitivePeerDependencies:
- '@types/react'
- react-dom
@@ -7305,7 +7327,7 @@ snapshots:
dequal@2.0.3: {}
- destr@2.0.3: {}
+ destr@2.0.5: {}
devlop@1.1.0:
dependencies:
@@ -7324,7 +7346,7 @@ snapshots:
'@babel/runtime': 7.26.10
csstype: 3.1.3
- dotenv@16.4.7: {}
+ dotenv@16.6.1: {}
dunder-proto@1.0.1:
dependencies:
@@ -7560,7 +7582,7 @@ snapshots:
optionalDependencies:
eslint-config-prettier: 10.1.2(eslint@9.26.0(jiti@1.21.7))
- eslint-plugin-react-hooks@4.6.2(eslint@9.26.0(jiti@1.21.7)):
+ eslint-plugin-react-hooks@5.2.0(eslint@9.26.0(jiti@1.21.7)):
dependencies:
eslint: 9.26.0(jiti@1.21.7)
@@ -7785,7 +7807,7 @@ snapshots:
object-assign: 4.1.1
promise: 7.3.1
setimmediate: 1.0.5
- ua-parser-js: 1.0.40
+ ua-parser-js: 1.0.41
transitivePeerDependencies:
- encoding
@@ -7827,11 +7849,11 @@ snapshots:
flatted@3.3.3: {}
- flux@4.0.4(react@18.3.1):
+ flux@4.0.4(react@19.1.1):
dependencies:
fbemitter: 3.0.0
fbjs: 3.0.5
- react: 18.3.1
+ react: 19.1.1
transitivePeerDependencies:
- encoding
@@ -7911,7 +7933,7 @@ snapshots:
citty: 0.1.6
consola: 3.4.2
defu: 6.1.4
- node-fetch-native: 1.6.6
+ node-fetch-native: 1.6.7
nypm: 0.5.4
pathe: 2.0.3
tar: 6.2.1
@@ -8819,12 +8841,12 @@ snapshots:
mkdirp@3.0.1: {}
- mlly@1.7.4:
+ mlly@1.8.0:
dependencies:
- acorn: 8.14.1
+ acorn: 8.15.0
pathe: 2.0.3
pkg-types: 1.3.1
- ufo: 1.5.4
+ ufo: 1.6.1
ms@2.1.3: {}
@@ -8865,12 +8887,12 @@ snapshots:
neo-async@2.6.2: {}
- next-themes@0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ next-themes@0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
- node-fetch-native@1.6.6: {}
+ node-fetch-native@1.6.7: {}
node-fetch@2.7.0:
dependencies:
@@ -8899,7 +8921,7 @@ snapshots:
pathe: 2.0.3
pkg-types: 1.3.1
tinyexec: 0.3.2
- ufo: 1.5.4
+ ufo: 1.6.1
object-assign@4.1.1: {}
@@ -9078,7 +9100,7 @@ snapshots:
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
- mlly: 1.7.4
+ mlly: 1.8.0
pathe: 2.0.3
pluralize@8.0.0: {}
@@ -9166,7 +9188,7 @@ snapshots:
rc9@2.1.2:
dependencies:
defu: 6.1.4
- destr: 2.0.3
+ destr: 2.0.5
react-base16-styling@0.6.0:
dependencies:
@@ -9175,64 +9197,63 @@ snapshots:
lodash.flow: 3.5.0
pure-color: 1.3.0
- react-chartjs-2@5.3.0(chart.js@4.4.9)(react@18.3.1):
+ react-chartjs-2@5.3.0(chart.js@4.4.9)(react@19.1.1):
dependencies:
chart.js: 4.4.9
- react: 18.3.1
+ react: 19.1.1
- react-dom@18.3.1(react@18.3.1):
+ react-dom@19.1.1(react@19.1.1):
dependencies:
- loose-envify: 1.4.0
- react: 18.3.1
- scheduler: 0.23.2
+ react: 19.1.1
+ scheduler: 0.26.0
- react-hook-form@7.56.2(react@18.3.1):
+ react-hook-form@7.56.2(react@19.1.1):
dependencies:
- react: 18.3.1
+ react: 19.1.1
- react-hotkeys-hook@4.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ react-hotkeys-hook@4.6.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
- react-i18next@15.5.1(i18next@25.1.2(typescript@5.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3):
+ react-i18next@15.5.1(i18next@25.1.2(typescript@5.8.3))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.8.3):
dependencies:
'@babel/runtime': 7.26.10
html-parse-stringify: 3.0.1
i18next: 25.1.2(typescript@5.8.3)
- react: 18.3.1
+ react: 19.1.1
optionalDependencies:
- react-dom: 18.3.1(react@18.3.1)
+ react-dom: 19.1.1(react@19.1.1)
typescript: 5.8.3
- react-icons@5.5.0(react@18.3.1):
+ react-icons@5.5.0(react@19.1.1):
dependencies:
- react: 18.3.1
+ react: 19.1.1
- react-innertext@1.1.5(@types/react@18.3.19)(react@18.3.1):
+ react-innertext@1.1.5(@types/react@18.3.19)(react@19.1.1):
dependencies:
'@types/react': 18.3.19
- react: 18.3.1
+ react: 19.1.1
react-is@16.13.1: {}
react-is@17.0.2: {}
- react-json-view@1.21.3(@types/react@18.3.19)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ react-json-view@1.21.3(@types/react@18.3.19)(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
- flux: 4.0.4(react@18.3.1)
- react: 18.3.1
+ flux: 4.0.4(react@19.1.1)
+ react: 19.1.1
react-base16-styling: 0.6.0
- react-dom: 18.3.1(react@18.3.1)
+ react-dom: 19.1.1(react@19.1.1)
react-lifecycles-compat: 3.0.4
- react-textarea-autosize: 8.5.8(@types/react@18.3.19)(react@18.3.1)
+ react-textarea-autosize: 8.5.9(@types/react@18.3.19)(react@19.1.1)
transitivePeerDependencies:
- '@types/react'
- encoding
react-lifecycles-compat@3.0.4: {}
- react-markdown@9.1.0(@types/react@18.3.19)(react@18.3.1):
+ react-markdown@9.1.0(@types/react@18.3.19)(react@19.1.1):
dependencies:
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
@@ -9241,7 +9262,7 @@ snapshots:
hast-util-to-jsx-runtime: 2.3.6
html-url-attributes: 3.0.1
mdast-util-to-hast: 13.2.0
- react: 18.3.1
+ react: 19.1.1
remark-parse: 11.0.0
remark-rehype: 11.1.1
unified: 11.0.5
@@ -9250,71 +9271,69 @@ snapshots:
transitivePeerDependencies:
- supports-color
- react-resizable-panels@2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ react-resizable-panels@2.1.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
- react-router-dom@6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ react-router-dom@6.30.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
'@remix-run/router': 1.23.0
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- react-router: 6.30.0(react@18.3.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ react-router: 6.30.0(react@19.1.1)
- react-router@6.30.0(react@18.3.1):
+ react-router@6.30.0(react@19.1.1):
dependencies:
'@remix-run/router': 1.23.0
- react: 18.3.1
+ react: 19.1.1
- react-select@5.10.1(@types/react@18.3.19)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ react-select@5.10.1(@types/react@18.3.19)(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
'@babel/runtime': 7.26.10
'@emotion/cache': 11.14.0
- '@emotion/react': 11.14.0(@types/react@18.3.19)(react@18.3.1)
+ '@emotion/react': 11.14.0(@types/react@18.3.19)(react@19.1.1)
'@floating-ui/dom': 1.6.13
'@types/react-transition-group': 4.4.12(@types/react@18.3.19)
memoize-one: 6.0.0
prop-types: 15.8.1
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- use-isomorphic-layout-effect: 1.2.0(@types/react@18.3.19)(react@18.3.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ react-transition-group: 4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ use-isomorphic-layout-effect: 1.2.0(@types/react@18.3.19)(react@19.1.1)
transitivePeerDependencies:
- '@types/react'
- supports-color
- react-syntax-highlighter@15.6.1(react@18.3.1):
+ react-syntax-highlighter@15.6.1(react@19.1.1):
dependencies:
'@babel/runtime': 7.26.10
highlight.js: 10.7.3
highlightjs-vue: 1.0.0
lowlight: 1.20.0
prismjs: 1.30.0
- react: 18.3.1
+ react: 19.1.1
refractor: 3.6.0
- react-textarea-autosize@8.5.8(@types/react@18.3.19)(react@18.3.1):
+ react-textarea-autosize@8.5.9(@types/react@18.3.19)(react@19.1.1):
dependencies:
- '@babel/runtime': 7.26.10
- react: 18.3.1
- use-composed-ref: 1.4.0(@types/react@18.3.19)(react@18.3.1)
- use-latest: 1.3.0(@types/react@18.3.19)(react@18.3.1)
+ '@babel/runtime': 7.28.4
+ react: 19.1.1
+ use-composed-ref: 1.4.0(@types/react@18.3.19)(react@19.1.1)
+ use-latest: 1.3.0(@types/react@18.3.19)(react@19.1.1)
transitivePeerDependencies:
- '@types/react'
- react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ react-transition-group@4.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
'@babel/runtime': 7.26.10
dom-helpers: 5.2.1
loose-envify: 1.4.0
prop-types: 15.8.1
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
- react@18.3.1:
- dependencies:
- loose-envify: 1.4.0
+ react@19.1.1: {}
read-pkg-up@7.0.1:
dependencies:
@@ -9493,9 +9512,7 @@ snapshots:
safer-buffer@2.1.2: {}
- scheduler@0.23.2:
- dependencies:
- loose-envify: 1.4.0
+ scheduler@0.26.0: {}
semver@5.7.2: {}
@@ -9856,9 +9873,9 @@ snapshots:
typescript@5.8.3: {}
- ua-parser-js@1.0.40: {}
+ ua-parser-js@1.0.41: {}
- ufo@1.5.4: {}
+ ufo@1.6.1: {}
uglify-js@3.19.3:
optional: true
@@ -9928,37 +9945,43 @@ snapshots:
querystringify: 2.2.0
requires-port: 1.0.0
- use-composed-ref@1.4.0(@types/react@18.3.19)(react@18.3.1):
+ use-composed-ref@1.4.0(@types/react@18.3.19)(react@19.1.1):
dependencies:
- react: 18.3.1
+ react: 19.1.1
optionalDependencies:
'@types/react': 18.3.19
- use-debounce@10.0.4(react@18.3.1):
+ use-debounce@10.0.4(react@19.1.1):
+ dependencies:
+ react: 19.1.1
+
+ use-isomorphic-layout-effect@1.2.0(@types/react@18.3.19)(react@19.1.1):
dependencies:
- react: 18.3.1
+ react: 19.1.1
+ optionalDependencies:
+ '@types/react': 18.3.19
- use-isomorphic-layout-effect@1.2.0(@types/react@18.3.19)(react@18.3.1):
+ use-isomorphic-layout-effect@1.2.1(@types/react@18.3.19)(react@19.1.1):
dependencies:
- react: 18.3.1
+ react: 19.1.1
optionalDependencies:
'@types/react': 18.3.19
- use-latest@1.3.0(@types/react@18.3.19)(react@18.3.1):
+ use-latest@1.3.0(@types/react@18.3.19)(react@19.1.1):
dependencies:
- react: 18.3.1
- use-isomorphic-layout-effect: 1.2.0(@types/react@18.3.19)(react@18.3.1)
+ react: 19.1.1
+ use-isomorphic-layout-effect: 1.2.1(@types/react@18.3.19)(react@19.1.1)
optionalDependencies:
'@types/react': 18.3.19
- use-sync-external-store@1.4.0(react@18.3.1):
+ use-sync-external-store@1.4.0(react@19.1.1):
dependencies:
- react: 18.3.1
+ react: 19.1.1
- usehooks-ts@3.1.1(react@18.3.1):
+ usehooks-ts@3.1.1(react@19.1.1):
dependencies:
lodash.debounce: 4.0.8
- react: 18.3.1
+ react: 19.1.1
validate-npm-package-license@3.0.4:
dependencies:
@@ -10169,17 +10192,17 @@ snapshots:
zod@3.24.4: {}
- zustand@4.5.6(@types/react@18.3.19)(react@18.3.1):
+ zustand@4.5.6(@types/react@18.3.19)(react@19.1.1):
dependencies:
- use-sync-external-store: 1.4.0(react@18.3.1)
+ use-sync-external-store: 1.4.0(react@19.1.1)
optionalDependencies:
'@types/react': 18.3.19
- react: 18.3.1
+ react: 19.1.1
- zustand@5.0.4(@types/react@18.3.19)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)):
+ zustand@5.0.4(@types/react@18.3.19)(react@19.1.1)(use-sync-external-store@1.4.0(react@19.1.1)):
optionalDependencies:
'@types/react': 18.3.19
- react: 18.3.1
- use-sync-external-store: 1.4.0(react@18.3.1)
+ react: 19.1.1
+ use-sync-external-store: 1.4.0(react@19.1.1)
zwitch@2.0.4: {}
diff --git a/airflow-core/src/airflow/ui/src/context/colorMode/ColorModeProvider.tsx b/airflow-core/src/airflow/ui/src/context/colorMode/ColorModeProvider.tsx
index 4839856c8ee..450dee35760 100644
--- a/airflow-core/src/airflow/ui/src/context/colorMode/ColorModeProvider.tsx
+++ b/airflow-core/src/airflow/ui/src/context/colorMode/ColorModeProvider.tsx
@@ -16,8 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { ThemeProvider } from "next-themes";
-import type { ThemeProviderProps } from "next-themes/dist/types";
+import { ThemeProvider, type ThemeProviderProps } from "next-themes";
export const ColorModeProvider = (props: ThemeProviderProps) => (
<ThemeProvider attribute="class" disableTransitionOnChange {...props} />
diff --git a/dev/react-plugin-tools/react_plugin_template/package.json b/dev/react-plugin-tools/react_plugin_template/package.json
index 5ba20233f12..c7d99bcf4af 100644
--- a/dev/react-plugin-tools/react_plugin_template/package.json
+++ b/dev/react-plugin-tools/react_plugin_template/package.json
@@ -31,9 +31,9 @@
"@chakra-ui/anatomy": "^2.3.4",
"@chakra-ui/react": "^3.20.0",
"@emotion/react": "^11.14.0",
- "next-themes": "^0.3.0",
- "react": "^18.3.1",
- "react-dom": "^18.3.1"
+ "next-themes": "^0.4.6",
+ "react": "^19.1.1",
+ "react-dom": "^19.1.1"
},
"devDependencies": {
"@eslint/compat": "^1.2.9",
@@ -52,7 +52,7 @@
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-react": "^7.37.5",
- "eslint-plugin-react-hooks": "^4.6.2",
+ "eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-perfectionist": "^4.12.3",
"eslint-plugin-react-refresh": "^0.4.20",
"eslint-plugin-unicorn": "^55.0.0",
diff --git a/dev/react-plugin-tools/react_plugin_template/src/context/colorMode/ColorModeProvider.tsx b/dev/react-plugin-tools/react_plugin_template/src/context/colorMode/ColorModeProvider.tsx
index 2b0c93cd403..96c29fc6031 100644
--- a/dev/react-plugin-tools/react_plugin_template/src/context/colorMode/ColorModeProvider.tsx
+++ b/dev/react-plugin-tools/react_plugin_template/src/context/colorMode/ColorModeProvider.tsx
@@ -17,8 +17,7 @@
* under the License.
*/
-import { ThemeProvider } from "next-themes";
-import type { ThemeProviderProps } from "next-themes/dist/types";
+import { ThemeProvider, type ThemeProviderProps } from "next-themes";
export const ColorModeProvider = (props: ThemeProviderProps) => (
<ThemeProvider attribute="class" disableTransitionOnChange {...props} />
(airflow) 21/22: Separate GoLang precommit after prek supports monorepo setup (#54615)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit caa07e2c12aee501639e44d2525542778973e865
Author: Jarek Potiuk <ja...@potiuk.com>
AuthorDate: Sun Sep 14 16:20:58 2025 +0000
Separate GoLang precommit after prek supports monorepo setup (#54615)
Prek 0.2.0 supports monorepo setup wieth separate pre-commit files
and as the first step of separation we separate golang pre-commits.
(cherry picked from commit d8e2c8559282508435647999fccd58c22c9108d9)
---
.github/actions/install-prek/action.yml | 2 +-
.github/workflows/ci-amd.yml | 2 +-
.pre-commit-config.yaml | 45 +----
contributing-docs/08_static_code_checks.rst | 4 +-
{.github => dev}/.pre-commit-config.yaml | 0
go-sdk/.pre-commit-config.yaml | 73 +++++++
scripts/ci/prek/upgrade_important_versions.py | 262 +++++++++++++++-----------
7 files changed, 235 insertions(+), 153 deletions(-)
diff --git a/.github/actions/install-prek/action.yml b/.github/actions/install-prek/action.yml
index cacd19f9639..9289c000aaa 100644
--- a/.github/actions/install-prek/action.yml
+++ b/.github/actions/install-prek/action.yml
@@ -64,7 +64,7 @@ runs:
uses: apache/infrastructure-actions/stash/restore@1c35b5ccf8fba5d4c3fdf25a045ca91aa0cbc468
with:
# yamllint disable rule:line-length
- key: cache-prek-v6-${{ inputs.platform }}-${{ inputs.python-version }}-${{inputs.skip-prek-hooks}}-${{ hashFiles('.pre-commit-config.yaml') }}
+ key: cache-prek-v6-${{ inputs.platform }}-${{ inputs.python-version }}-${{inputs.skip-prek-hooks}}-${{ hashFiles('**/.pre-commit-config.yaml') }}
path: /tmp/
id: restore-prek-cache
- name: "Check if prek cache tarball exists"
diff --git a/.github/workflows/ci-amd.yml b/.github/workflows/ci-amd.yml
index e65bc71e0fc..e7bd7010858 100644
--- a/.github/workflows/ci-amd.yml
+++ b/.github/workflows/ci-amd.yml
@@ -193,7 +193,7 @@ jobs:
skip-prek-hooks: ""
- name: "Run pin-versions"
run: >
- prek -c .github/.pre-commit-config.yaml --all-files --verbose --hook-stage manual
+ prek -c dev/.pre-commit-config.yaml --all-files --verbose --hook-stage manual
pin-versions
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 93a75476896..4f008939a76 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -19,8 +19,7 @@ default_stages: [pre-commit, pre-push]
default_language_version:
python: python3
node: 22.19.0
- golang: 1.24.0
-minimum_prek_version: '0.1.3'
+minimum_prek_version: '0.2.0'
exclude: ^.*/.*_vendor/
repos:
- repo: meta
@@ -172,18 +171,6 @@ repos:
- --fuzzy-match-generates-todo
files: >
\.cfg$|\.conf$|\.ini$|\.ldif$|\.properties$|\.service$|\.tf$|Dockerfile.*$
- - id: insert-license
- name: Add license for all Go files
- types: [go]
- exclude: mocks/.*\.go$
- args:
- - --comment-style
- - "|//|"
- - --license-filepath
- - scripts/ci/license-templates/LICENSE.txt
- - --insert-license-after-regex
- # We need this 'generated by' line at the top for `golines` to not format it
- - '// Code generated by .*'
- repo: local
hooks:
- id: check-min-python-version
@@ -1303,36 +1290,6 @@ repos:
files: ^airflow-core/src/airflow/migrations/versions/.*\.py$
exclude:
^airflow-core/src/airflow/migrations/versions/0028_3_0_0_drop_ab_user_id_foreign_key.py$
- - id: go-mockery
- name: Generate mocks for go
- entry: -w /src/go-sdk vektra/mockery:3
- files: ^go-sdk/
- exclude: mocks/.*\.go$
- types: [go]
- pass_filenames: false
- language: docker_image
- - id: go-mod-tidy
- name: Run go mod tidy
- entry: bash -c "cd go-sdk && go mod tidy"
- files: ^go-sdk/
- exclude: mocks/.*\.go$
- pass_filenames: false
- language: system
- - id: gofmt
- name: Format go code
- entry: golines --base-formatter=gofumpt --write-output --max-len=100 --chain-split-dots
- additional_dependencies: [github.com/segmentio/golines@latest, mvdan.cc/gofumpt@v0.8.0]
- files: ^go-sdk/
- types: [go]
- language: golang
- - id: gci
- name: Consistent import ordering for Go files
- # Since this is invoked from the root folder, not go-sdk/, gci can't auto-detect the prefix
- entry: gci write --skip-generated -s standard -s default -s "prefix(github.com/apache/airflow)"
- additional_dependencies: [github.com/daixiang0/gci@v0.13.6]
- files: ^go-sdk/
- types: [go]
- language: golang
- id: ts-compile-lint-ui
name: Compile / format / lint UI
description: TS types generation / ESLint / Prettier new UI files
diff --git a/contributing-docs/08_static_code_checks.rst b/contributing-docs/08_static_code_checks.rst
index 5e910a31a6b..297b2492830 100644
--- a/contributing-docs/08_static_code_checks.rst
+++ b/contributing-docs/08_static_code_checks.rst
@@ -275,14 +275,14 @@ GitHub Actions in the CI workflows.
This action requires ``GITHUB_TOKEN`` to be set, otherwise you might hit the rate limits with GitHub API, it
is also configured in a separate ``.prek-config.yaml`` file in the
-``.github`` directory as it requires Python 3.11 to run. It is not run automatically
+``dev`` directory as it requires Python 3.11 to run. It is not run automatically
when you commit the code but in runs as a separate job in the CI. However, you can run it
manually by running:
.. code-block:: bash
export GITHUB_TOKEN=YOUR_GITHUB_TOKEN
- prek -c .github/.pre-commit-config.yaml --all-files --hook-stage manual --verbose
+ prek -c dev/.pre-commit-config.yaml --all-files --hook-stage manual --verbose
Mypy checks
diff --git a/.github/.pre-commit-config.yaml b/dev/.pre-commit-config.yaml
similarity index 100%
rename from .github/.pre-commit-config.yaml
rename to dev/.pre-commit-config.yaml
diff --git a/go-sdk/.pre-commit-config.yaml b/go-sdk/.pre-commit-config.yaml
new file mode 100644
index 00000000000..4154c080168
--- /dev/null
+++ b/go-sdk/.pre-commit-config.yaml
@@ -0,0 +1,73 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+---
+default_stages: [pre-commit, pre-push]
+default_language_version:
+ golang: 1.24.0
+minimum_prek_version: '0.0.28'
+repos:
+ - repo: meta
+ hooks:
+ - id: identity
+ name: Print checked files
+ description: Print input to the static check hooks for troubleshooting
+ - id: check-hooks-apply
+ name: Check if all hooks apply to the repository
+ - repo: https://github.com/Lucas-C/pre-commit-hooks
+ # replace hash with version once PR #103 merged comes in a release
+ rev: abdd8b62891099da34162217ecb3872d22184a51
+ hooks:
+ - id: insert-license
+ name: Add license for all Go files
+ types: [go]
+ exclude: mocks/.*\.go$
+ args:
+ - --comment-style
+ - "|//|"
+ - --license-filepath
+ - ../scripts/ci/license-templates/LICENSE.txt
+ - --insert-license-after-regex
+ # We need this 'generated by' line at the top for `golines` to not format it
+ - '// Code generated by .*'
+ - repo: local
+ hooks:
+ - id: go-mockery
+ name: Generate mocks for go
+ entry: vektra/mockery:3
+ exclude: mocks/.*\.go$
+ types: [go]
+ pass_filenames: false
+ language: docker_image
+ - id: go-mod-tidy
+ name: Run go mod tidy
+ entry: go mod tidy
+ types: [go]
+ exclude: mocks/.*\.go$
+ pass_filenames: false
+ language: golang
+ - id: gofmt
+ name: Format go code
+ entry: golines --base-formatter=gofumpt --write-output --max-len=100 --chain-split-dots
+ additional_dependencies: [github.com/segmentio/golines@latest, mvdan.cc/gofumpt@v0.8.0]
+ types: [go]
+ language: golang
+ - id: gci
+ name: Consistent import ordering for Go files
+ entry: gci write --skip-generated -s standard -s default -s localmodule
+ additional_dependencies: [github.com/daixiang0/gci@v0.13.6]
+ types: [go]
+ language: golang
diff --git a/scripts/ci/prek/upgrade_important_versions.py b/scripts/ci/prek/upgrade_important_versions.py
index 75dc99e1be2..779c692b70b 100755
--- a/scripts/ci/prek/upgrade_important_versions.py
+++ b/scripts/ci/prek/upgrade_important_versions.py
@@ -24,6 +24,13 @@
# "rich>=13.6.0",
# ]
# ///
+#
+# DEBUGGING
+# * You can set UPGRADE_ALL_BY_DEFAULT to "false" to only upgrade those versions that
+# are set by UPGRADE_NNNNNNN (NNNNNN > thing to upgrade version)
+# * You can set VERBOSE="true" to see requests being made
+# * You can set UPGRADE_NNNNNNN_INCLUDE_PRE_RELEASES="true"
+
from __future__ import annotations
import os
@@ -79,46 +86,82 @@ for file in DOCKER_IMAGES_EXAMPLE_DIR_PATH.rglob("*.sh"):
FILES_TO_UPDATE.append((file, False))
-def get_latest_pypi_version(package_name: str) -> str:
+def get_latest_pypi_version(package_name: str, should_upgrade: bool) -> str:
+ if not should_upgrade:
+ return ""
+ if VERBOSE:
+ console.print(f"[bright_blue]Fetching latest version for {package_name} from PyPI")
response = requests.get(
f"https://pypi.org/pypi/{package_name}/json", headers={"User-Agent": "Python requests"}
)
response.raise_for_status() # Ensure we got a successful response
data = response.json()
- latest_version = data["info"]["version"] # The version info is under the 'info' key
+ if os.environ.get(f"UPGRADE_{package_name.upper()}_INCLUDE_PRE_RELEASES", ""):
+ latest_version = str(sorted([Version(version) for version in data["releases"].keys()])[-1])
+ else:
+ latest_version = data["info"]["version"] # The version info is under the 'info' key
+ if VERBOSE:
+ console.print(f"[bright_blue]Latest version for {package_name}: {latest_version}")
return latest_version
-def get_latest_python_version(python_major_minor: str, github_token: str | None) -> str | None:
- latest_version = None
- # Matches versions of vA.B.C and vA.B where C can only be numeric and v is optional
- version_match = re.compile(rf"^v?{python_major_minor}\.?\d*$")
+def get_all_python_versions() -> list[Version]:
+ if VERBOSE:
+ console.print("[bright_blue]Fetching all released Python versions from python.org")
+ url = "https://www.python.org/api/v2/downloads/release/?is_published=true"
headers = {"User-Agent": "Python requests"}
- if github_token:
- headers["Authorization"] = f"Bearer {github_token}"
- for i in range(5):
- response = requests.get(
- f"https://api.github.com/repos/python/cpython/tags?per_page=100&page={i + 1}",
- headers=headers,
- )
- response.raise_for_status() # Ensure we got a successful response
- data = response.json()
- versions = [str(tag["name"]) for tag in data if version_match.match(tag.get("name", ""))]
- if versions:
- latest_version = sorted(versions, key=Version, reverse=True)[0]
- break
- return latest_version[1:] if latest_version and latest_version.startswith("v") else latest_version
+ response = requests.get(url, headers=headers)
+ response.raise_for_status()
+ data = response.json()
+ versions = []
+ matcher = re.compile(r"^Python ([\d.]+$)")
+ for release in data:
+ release_name = release["name"]
+ match = matcher.match(release_name)
+ if match:
+ versions.append(Version(match.group(1)))
+ return versions
+
+
+def get_latest_python_version(python_major_minor: str, all_versions: list[Version]) -> str:
+ """
+ Fetch the latest released Python version for a given major.minor (e.g. '3.12') using python.org API.
+ Much faster than paginating through all GitHub tags.
+ """
+ # Only consider releases matching the major.minor.patch pattern
+ matching = [
+ version for version in all_versions if python_major_minor == f"{version.major}.{version.minor}"
+ ]
+ if not matching:
+ console.print(f"[bright_red]No released Python versions found for {python_major_minor}")
+ sys.exit(1)
+ # Sort and return the latest version
+ latest_version = sorted(matching)[-1]
+ if VERBOSE:
+ console.print(f"[bright_blue]Latest version for {python_major_minor}: {latest_version}")
+ return str(latest_version)
def get_latest_golang_version() -> str:
+ if not UPGRADE_GOLANG:
+ return ""
+ if VERBOSE:
+ console.print("[bright_blue]Fetching latest Go version from go.dev")
response = requests.get("https://go.dev/dl/?mode=json")
response.raise_for_status() # Ensure we got a successful response
versions = response.json()
stable_versions = [release["version"].replace("go", "") for release in versions if release["stable"]]
- return sorted(stable_versions, key=Version, reverse=True)[0]
+ latest_version = sorted(stable_versions, key=Version, reverse=True)[0]
+ if VERBOSE:
+ console.print(f"[bright_blue]Latest version for Go: {latest_version}")
+ return latest_version
def get_latest_lts_node_version() -> str:
+ if not UPGRADE_NODE_LTS:
+ return ""
+ if VERBOSE:
+ console.print("[bright_blue]Fetching latest LTS Node version from nodejs.org")
response = requests.get("https://nodejs.org/dist/index.json")
response.raise_for_status() # Ensure we got a successful response
versions = response.json()
@@ -126,7 +169,10 @@ def get_latest_lts_node_version() -> str:
lts_versions = [version["version"] for version in versions if version["version"].startswith(lts_prefix)]
# The json array is sorted from newest to oldest, so the first element is the latest LTS version
# Skip leading v in version
- return lts_versions[0][1:]
+ latest_version = lts_versions[0][1:]
+ if VERBOSE:
+ console.print(f"[bright_blue]Latest version for LTS Node: {latest_version}")
+ return latest_version
class Quoting(Enum):
@@ -138,38 +184,44 @@ class Quoting(Enum):
PIP_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
- (re.compile(r"(AIRFLOW_PIP_VERSION=)([0-9.]+)"), Quoting.UNQUOTED),
- (re.compile(r"(python -m pip install --upgrade pip==)([0-9.]+)"), Quoting.UNQUOTED),
- (re.compile(r"(AIRFLOW_PIP_VERSION = )(\"[0-9.]+\")"), Quoting.DOUBLE_QUOTED),
- (re.compile(r"(PIP_VERSION = )(\"[0-9.]+\")"), Quoting.DOUBLE_QUOTED),
- (re.compile(r"(PIP_VERSION=)(\"[0-9.]+\")"), Quoting.DOUBLE_QUOTED),
- (re.compile(r"(\| *`AIRFLOW_PIP_VERSION` *\| *)(`[0-9.]+`)( *\|)"), Quoting.REVERSE_SINGLE_QUOTED),
+ (re.compile(r"(AIRFLOW_PIP_VERSION=)([0-9.abrc]+)"), Quoting.UNQUOTED),
+ (re.compile(r"(python -m pip install --upgrade pip==)([0-9.abrc]+)"), Quoting.UNQUOTED),
+ (re.compile(r"(AIRFLOW_PIP_VERSION = )(\"[0-9.abrc]+\")"), Quoting.DOUBLE_QUOTED),
+ (re.compile(r"(PIP_VERSION = )(\"[0-9.abrc]+\")"), Quoting.DOUBLE_QUOTED),
+ (re.compile(r"(PIP_VERSION=)(\"[0-9.abrc]+\")"), Quoting.DOUBLE_QUOTED),
+ (re.compile(r"(\| *`AIRFLOW_PIP_VERSION` *\| *)(`[0-9.abrc]+`)( *\|)"), Quoting.REVERSE_SINGLE_QUOTED),
]
PYTHON_PATTERNS: list[tuple[str, Quoting]] = [
- (r"(\"{python_major_minor}\": \")([0-9.]+)(\")", Quoting.UNQUOTED),
+ (r"(\"{python_major_minor}\": \")([0-9.abrc]+)(\")", Quoting.UNQUOTED),
]
GOLANG_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
- (re.compile(r"(GOLANG_MAJOR_MINOR_VERSION=)(\"[0-9.]+\")"), Quoting.DOUBLE_QUOTED),
- (re.compile(r"(\| *`GOLANG_MAJOR_MINOR_VERSION` *\| *)(`[0-9.]+`)( *\|)"), Quoting.REVERSE_SINGLE_QUOTED),
+ (re.compile(r"(GOLANG_MAJOR_MINOR_VERSION=)(\"[0-9.abrc]+\")"), Quoting.DOUBLE_QUOTED),
+ (
+ re.compile(r"(\| *`GOLANG_MAJOR_MINOR_VERSION` *\| *)(`[0-9.abrc]+`)( *\|)"),
+ Quoting.REVERSE_SINGLE_QUOTED,
+ ),
]
AIRFLOW_IMAGE_PYTHON_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
- (re.compile(r"(AIRFLOW_PYTHON_VERSION=)(\"[0-9.]+\")"), Quoting.DOUBLE_QUOTED),
- (re.compile(r"(\| ``AIRFLOW_PYTHON_VERSION`` *\| )(``[0-9.]+``)( *\|)"), Quoting.REVERSE_DOUBLE_QUOTED),
+ (re.compile(r"(AIRFLOW_PYTHON_VERSION=)(\"[0-9.abrc]+\")"), Quoting.DOUBLE_QUOTED),
+ (
+ re.compile(r"(\| ``AIRFLOW_PYTHON_VERSION`` *\| )(``[0-9.abrc]+``)( *\|)"),
+ Quoting.REVERSE_DOUBLE_QUOTED,
+ ),
]
UV_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
- (re.compile(r"(AIRFLOW_UV_VERSION=)([0-9.]+)"), Quoting.UNQUOTED),
- (re.compile(r"(uv>=)([0-9.]+)"), Quoting.UNQUOTED),
- (re.compile(r"(AIRFLOW_UV_VERSION = )(\"[0-9.]+\")"), Quoting.DOUBLE_QUOTED),
- (re.compile(r"^(\s*UV_VERSION = )(\"[0-9.]+\")", re.MULTILINE), Quoting.DOUBLE_QUOTED),
- (re.compile(r"^(\s*UV_VERSION=)(\"[0-9.]+\")", re.MULTILINE), Quoting.DOUBLE_QUOTED),
- (re.compile(r"(\| *`AIRFLOW_UV_VERSION` *\| *)(`[0-9.]+`)( *\|)"), Quoting.REVERSE_SINGLE_QUOTED),
+ (re.compile(r"(AIRFLOW_UV_VERSION=)([0-9.abrc]+)"), Quoting.UNQUOTED),
+ (re.compile(r"(uv>=)([0-9.abrc]+)"), Quoting.UNQUOTED),
+ (re.compile(r"(AIRFLOW_UV_VERSION = )(\"[0-9.abrc]+\")"), Quoting.DOUBLE_QUOTED),
+ (re.compile(r"^(\s*UV_VERSION = )(\"[0-9.abrc]+\")", re.MULTILINE), Quoting.DOUBLE_QUOTED),
+ (re.compile(r"^(\s*UV_VERSION=)(\"[0-9.abrc]+\")", re.MULTILINE), Quoting.DOUBLE_QUOTED),
+ (re.compile(r"(\| *`AIRFLOW_UV_VERSION` *\| *)(`[0-9.abrd]+`)( *\|)"), Quoting.REVERSE_SINGLE_QUOTED),
(
re.compile(
- r"(\")([0-9.]+)(\" # Keep this comment to "
+ r"(\")([0-9.abrc]+)(\" # Keep this comment to "
r"allow automatic replacement of uv version)"
),
Quoting.UNQUOTED,
@@ -177,18 +229,18 @@ UV_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
]
PREK_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
- (re.compile(r"(AIRFLOW_PREK_VERSION=)([0-9.]+)"), Quoting.UNQUOTED),
- (re.compile(r"(AIRFLOW_PREK_VERSION = )(\"[0-9.]+\")"), Quoting.DOUBLE_QUOTED),
- (re.compile(r"(prek>=)([0-9.]+)"), Quoting.UNQUOTED),
- (re.compile(r"(PREK_VERSION = )(\"[0-9.]+\")"), Quoting.DOUBLE_QUOTED),
- (re.compile(r"(PREK_VERSION=)(\"[0-9.]+\")"), Quoting.DOUBLE_QUOTED),
+ (re.compile(r"(AIRFLOW_PREK_VERSION=)([0-9.abrc]+)"), Quoting.UNQUOTED),
+ (re.compile(r"(AIRFLOW_PREK_VERSION = )(\"[0-9.abrc]+\")"), Quoting.DOUBLE_QUOTED),
+ (re.compile(r"(prek>=)([0-9.abrc]+)"), Quoting.UNQUOTED),
+ (re.compile(r"(PREK_VERSION = )(\"[0-9.abrc]+\")"), Quoting.DOUBLE_QUOTED),
+ (re.compile(r"(PREK_VERSION=)(\"[0-9.abrc]+\")"), Quoting.DOUBLE_QUOTED),
(
- re.compile(r"(\| *`AIRFLOW_PREK_VERSION` *\| *)(`[0-9.]+`)( *\|)"),
+ re.compile(r"(\| *`AIRFLOW_PREK_VERSION` *\| *)(`[0-9.abrc]+`)( *\|)"),
Quoting.REVERSE_SINGLE_QUOTED,
),
(
re.compile(
- r"(\")([0-9.]+)(\" # Keep this comment to allow automatic "
+ r"(\")([0-9.abrc]+)(\" # Keep this comment to allow automatic "
r"replacement of prek version)"
),
Quoting.UNQUOTED,
@@ -196,7 +248,7 @@ PREK_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
]
NODE_LTS_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
- (re.compile(r"( *node: )([0-9.]+)"), Quoting.UNQUOTED),
+ (re.compile(r"(^ node: )([0-9.abrc]+)^"), Quoting.UNQUOTED),
]
@@ -212,18 +264,25 @@ def get_replacement(value: str, quoting: Quoting) -> str:
return value
-UPGRADE_UV: bool = os.environ.get("UPGRADE_UV", "true").lower() == "true"
-UPGRADE_PIP: bool = os.environ.get("UPGRADE_PIP", "true").lower() == "true"
-UPGRADE_PYTHON: bool = os.environ.get("UPGRADE_PYTHON", "true").lower() == "true"
-UPGRADE_GOLANG: bool = os.environ.get("UPGRADE_GOLANG", "true").lower() == "true"
-UPGRADE_PREK: bool = os.environ.get("UPGRADE_PREK", "true").lower() == "true"
-UPGRADE_NODE_LTS: bool = os.environ.get("UPGRADE_NODE_LTS", "true").lower() == "true"
-UPGRADE_HATCH: bool = os.environ.get("UPGRADE_HATCH", "true").lower() == "true"
-UPGRADE_PYYAML: bool = os.environ.get("UPGRADE_PYYAML", "true").lower() == "true"
-UPGRADE_GITPYTHON: bool = os.environ.get("UPGRADE_GITPYTHON", "true").lower() == "true"
-UPGRADE_RICH: bool = os.environ.get("UPGRADE_RICH", "true").lower() == "true"
-UPGRADE_RUFF: bool = os.environ.get("UPGRADE_RUFF", "true").lower() == "true"
-UPGRADE_MYPY: bool = os.environ.get("UPGRADE_MYPY", "true").lower() == "true"
+VERBOSE: bool = os.environ.get("VERBOSE", "false") == "true"
+UPGRADE_ALL_BY_DEFAULT: bool = os.environ.get("UPGRADE_ALL_BY_DEFAULT", "true") == "true"
+UPGRADE_ALL_BY_DEFAULT_STR: str = str(UPGRADE_ALL_BY_DEFAULT).lower()
+if UPGRADE_ALL_BY_DEFAULT:
+ if VERBOSE == "true":
+ console.print("[bright_blue]Upgrading all important versions")
+
+UPGRADE_GITPYTHON: bool = os.environ.get("UPGRADE_GITPYTHON", UPGRADE_ALL_BY_DEFAULT_STR).lower() == "true"
+UPGRADE_GOLANG: bool = os.environ.get("UPGRADE_GOLANG", UPGRADE_ALL_BY_DEFAULT_STR).lower() == "true"
+UPGRADE_HATCH: bool = os.environ.get("UPGRADE_HATCH", UPGRADE_ALL_BY_DEFAULT_STR).lower() == "true"
+UPGRADE_NODE_LTS: bool = os.environ.get("UPGRADE_NODE_LTS", UPGRADE_ALL_BY_DEFAULT_STR).lower() == "true"
+UPGRADE_PIP: bool = os.environ.get("UPGRADE_PIP", UPGRADE_ALL_BY_DEFAULT_STR).lower() == "true"
+UPGRADE_PREK: bool = os.environ.get("UPGRADE_PREK", UPGRADE_ALL_BY_DEFAULT_STR).lower() == "true"
+UPGRADE_PYTHON: bool = os.environ.get("UPGRADE_PYTHON", UPGRADE_ALL_BY_DEFAULT_STR).lower() == "true"
+UPGRADE_PYYAML: bool = os.environ.get("UPGRADE_PYYAML", UPGRADE_ALL_BY_DEFAULT_STR).lower() == "true"
+UPGRADE_RICH: bool = os.environ.get("UPGRADE_RICH", UPGRADE_ALL_BY_DEFAULT_STR).lower() == "true"
+UPGRADE_RUFF: bool = os.environ.get("UPGRADE_RUFF", UPGRADE_ALL_BY_DEFAULT_STR).lower() == "true"
+UPGRADE_UV: bool = os.environ.get("UPGRADE_UV", UPGRADE_ALL_BY_DEFAULT_STR).lower() == "true"
+UPGRADE_MYPY: bool = os.environ.get("UPGRADE_MYPY", UPGRADE_ALL_BY_DEFAULT_STR).lower() == "true"
ALL_PYTHON_MAJOR_MINOR_VERSIONS = ["3.9", "3.10", "3.11", "3.12", "3.13"]
DEFAULT_PROD_IMAGE_PYTHON_VERSION = "3.12"
@@ -260,51 +319,53 @@ if __name__ == "__main__":
gh_token = retrieve_gh_token(description="airflow-upgrade-important-versions", scopes="public_repo")
changed = False
golang_version = get_latest_golang_version()
- pip_version = get_latest_pypi_version("pip")
- uv_version = get_latest_pypi_version("uv")
- setuptools_version = get_latest_pypi_version("setuptools")
- prek_version = get_latest_pypi_version("prek")
- hatch_version = get_latest_pypi_version("hatch")
- pyyaml_version = get_latest_pypi_version("PyYAML")
- gitpython_version = get_latest_pypi_version("GitPython")
- rich_version = get_latest_pypi_version("rich")
- ruff_version = get_latest_pypi_version("ruff")
+ pip_version = get_latest_pypi_version("pip", UPGRADE_PIP)
+ uv_version = get_latest_pypi_version("uv", UPGRADE_UV)
+ prek_version = get_latest_pypi_version("prek", UPGRADE_PREK)
+ hatch_version = get_latest_pypi_version("hatch", UPGRADE_HATCH)
+ pyyaml_version = get_latest_pypi_version("PyYAML", UPGRADE_PYYAML)
+ gitpython_version = get_latest_pypi_version("GitPython", UPGRADE_GITPYTHON)
+ ruff_version = get_latest_pypi_version("ruff", UPGRADE_RUFF)
+ rich_version = get_latest_pypi_version("rich", UPGRADE_RICH)
+ mypy_version = get_latest_pypi_version("mypy", UPGRADE_MYPY)
node_lts_version = get_latest_lts_node_version()
- mypy_version = get_latest_pypi_version("mypy")
+ latest_python_versions: dict[str, str] = {}
+ latest_image_python_version = ""
+ if UPGRADE_PYTHON:
+ all_python_versions = get_all_python_versions() if UPGRADE_PYTHON else None
+ for python_major_minor_version in ALL_PYTHON_MAJOR_MINOR_VERSIONS:
+ latest_python_versions[python_major_minor_version] = get_latest_python_version(
+ python_major_minor_version, all_python_versions
+ )
+ if python_major_minor_version == DEFAULT_PROD_IMAGE_PYTHON_VERSION:
+ latest_image_python_version = latest_python_versions[python_major_minor_version]
+ console.print(
+ f"[bright_blue]Latest image python {python_major_minor_version} version: {latest_image_python_version}"
+ )
for file, keep_length in FILES_TO_UPDATE:
console.print(f"[bright_blue]Updating {file}")
file_content = file.read_text()
new_content = file_content
if UPGRADE_PIP:
- console.print(f"[bright_blue]Latest pip version: {pip_version}")
for line_pattern, quoting in PIP_PATTERNS:
new_content = replace_version(
line_pattern, get_replacement(pip_version, quoting), new_content, keep_length
)
if UPGRADE_PYTHON:
- for python_version in ALL_PYTHON_MAJOR_MINOR_VERSIONS:
- latest_python_version = get_latest_python_version(python_version, gh_token)
- if not latest_python_version:
- console.print(
- f"[red]Failed to get latest version for python {python_version}. Skipping.[/]"
- )
- sys.exit(1)
- console.print(f"[bright_blue]Latest python {python_version} version: {latest_python_version}")
+ for python_major_minor_version in ALL_PYTHON_MAJOR_MINOR_VERSIONS:
+ latest_python_version = latest_python_versions[python_major_minor_version]
for line_format, quoting in PYTHON_PATTERNS:
- line_pattern = re.compile(line_format.format(python_major_minor=python_version))
- console.print(line_pattern)
+ line_pattern = re.compile(
+ line_format.format(python_major_minor=python_major_minor_version)
+ )
new_content = replace_version(
line_pattern,
get_replacement(latest_python_version, quoting),
new_content,
keep_length,
)
- if python_version == DEFAULT_PROD_IMAGE_PYTHON_VERSION:
- console.print(
- f"[bright_blue]Latest image python {python_version} version: {latest_python_version}"
- )
+ if python_major_minor_version == DEFAULT_PROD_IMAGE_PYTHON_VERSION:
for line_pattern, quoting in AIRFLOW_IMAGE_PYTHON_PATTERNS:
- console.print(line_pattern)
new_content = replace_version(
line_pattern,
get_replacement(latest_python_version, quoting),
@@ -312,25 +373,21 @@ if __name__ == "__main__":
keep_length,
)
if UPGRADE_GOLANG:
- console.print(f"[bright_blue]Latest golang version: {golang_version}")
for line_pattern, quoting in GOLANG_PATTERNS:
new_content = replace_version(
line_pattern, get_replacement(golang_version, quoting), new_content, keep_length
)
if UPGRADE_UV:
- console.print(f"[bright_blue]Latest uv version: {uv_version}")
for line_pattern, quoting in UV_PATTERNS:
new_content = replace_version(
line_pattern, get_replacement(uv_version, quoting), new_content, keep_length
)
if UPGRADE_PREK:
- console.print(f"[bright_blue]Latest prek version: {prek_version}")
for line_pattern, quoting in PREK_PATTERNS:
new_content = replace_version(
line_pattern, get_replacement(prek_version, quoting), new_content, keep_length
)
if UPGRADE_NODE_LTS:
- console.print(f"[bright_blue]Latest Node LTS version: {node_lts_version}")
for line_pattern, quoting in NODE_LTS_PATTERNS:
new_content = replace_version(
line_pattern,
@@ -339,62 +396,57 @@ if __name__ == "__main__":
keep_length,
)
if UPGRADE_HATCH:
- console.print(f"[bright_blue]Latest hatch version: {hatch_version}")
new_content = re.sub(
- r"(HATCH_VERSION = )(\"[0-9.]+\")",
+ r"(HATCH_VERSION = )(\"[0-9.abrc]+\")",
f'HATCH_VERSION = "{hatch_version}"',
new_content,
)
new_content = re.sub(
- r"(HATCH_VERSION=)(\"[0-9.]+\")",
+ r"(HATCH_VERSION=)(\"[0-9.abrc]+\")",
f'HATCH_VERSION="{hatch_version}"',
new_content,
)
if UPGRADE_PYYAML:
- console.print(f"[bright_blue]Latest PyYAML version: {pyyaml_version}")
new_content = re.sub(
- r"(PYYAML_VERSION = )(\"[0-9.]+\")",
+ r"(PYYAML_VERSION = )(\"[0-9.abrc]+\")",
f'PYYAML_VERSION = "{pyyaml_version}"',
new_content,
)
new_content = re.sub(
- r"(PYYAML_VERSION=)(\"[0-9.]+\")",
+ r"(PYYAML_VERSION=)(\"[0-9.abrc]+\")",
f'PYYAML_VERSION="{pyyaml_version}"',
new_content,
)
if UPGRADE_GITPYTHON:
- console.print(f"[bright_blue]Latest GitPython version: {gitpython_version}")
new_content = re.sub(
- r"(GITPYTHON_VERSION = )(\"[0-9.]+\")",
+ r"(GITPYTHON_VERSION = )(\"[0-9.abrc]+\")",
f'GITPYTHON_VERSION = "{gitpython_version}"',
new_content,
)
new_content = re.sub(
- r"(GITPYTHON_VERSION=)(\"[0-9.]+\")",
+ r"(GITPYTHON_VERSION=)(\"[0-9.abrc]+\")",
f'GITPYTHON_VERSION="{gitpython_version}"',
new_content,
)
if UPGRADE_RICH:
- console.print(f"[bright_blue]Latest rich version: {rich_version}")
new_content = re.sub(
- r"(RICH_VERSION = )(\"[0-9.]+\")",
+ r"(RICH_VERSION = )(\"[0-9.abrc]+\")",
f'RICH_VERSION = "{rich_version}"',
new_content,
)
new_content = re.sub(
- r"(RICH_VERSION=)(\"[0-9.]+\")",
+ r"(RICH_VERSION=)(\"[0-9.abrc]+\")",
f'RICH_VERSION="{rich_version}"',
new_content,
)
if UPGRADE_RUFF:
- console.print(f"[bright_blue]Latest ruff version: {ruff_version}")
new_content = re.sub(
- r"(ruff==)([0-9.]+)",
+ r"(ruff==)([0-9.abrc]+)",
f"ruff=={ruff_version}",
new_content,
)
new_content = re.sub(
- r"(ruff>=)([0-9.]+)",
+ r"(ruff>=)([0-9.abrc]+)",
f"ruff>={ruff_version}",
new_content,
)
(airflow) 16/22: Prevent HITL from emitting reqeust for settled dag and tasks (#55575)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 73f25ace72dfb44cef258cf5c92f4028e42b4284
Author: Pierre Jeambrun <pi...@gmail.com>
AuthorDate: Fri Sep 12 09:55:12 2025 -0600
Prevent HITL from emitting reqeust for settled dag and tasks (#55575)
(cherry picked from commit 4c85e279d619217557803b534f3d75ffee958228)
---
airflow-core/src/airflow/ui/src/components/NeedsReviewButton.tsx | 6 ++++--
airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx | 8 +++++++-
airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx | 7 ++++++-
3 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/components/NeedsReviewButton.tsx b/airflow-core/src/airflow/ui/src/components/NeedsReviewButton.tsx
index 3ceadb7b55a..fcec3a8fe1d 100644
--- a/airflow-core/src/airflow/ui/src/components/NeedsReviewButton.tsx
+++ b/airflow-core/src/airflow/ui/src/components/NeedsReviewButton.tsx
@@ -27,14 +27,16 @@ import { StatsCard } from "./StatsCard";
export const NeedsReviewButton = ({
dagId,
+ refreshInterval,
runId,
taskId,
}: {
readonly dagId?: string;
+ readonly refreshInterval?: number | false;
readonly runId?: string;
readonly taskId?: string;
}) => {
- const refetchInterval = useAutoRefresh({ dagId });
+ const hookAutoRefresh = useAutoRefresh({ dagId });
const { data: hitlStatsData, isLoading } = useHumanInTheLoopServiceGetHitlDetails(
{
dagId,
@@ -45,7 +47,7 @@ export const NeedsReviewButton = ({
},
undefined,
{
- refetchInterval,
+ refetchInterval: refreshInterval ?? hookAutoRefresh,
},
);
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
index 8cebcc4ba0b..8de8220a417 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Overview/Overview.tsx
@@ -35,6 +35,7 @@ import TimeRangeSelector from "src/components/TimeRangeSelector";
import { TrendCountButton } from "src/components/TrendCountButton";
import { SearchParamsKeys } from "src/constants/searchParams";
import { useGridRuns } from "src/queries/useGridRuns.ts";
+import { isStatePending, useAutoRefresh } from "src/utils";
const FailedLogs = lazy(() => import("./FailedLogs"));
@@ -75,9 +76,14 @@ export const Overview = () => {
timestampLte: endDate,
});
+ const refetchInterval = useAutoRefresh({});
+
return (
<Box m={4} spaceY={4}>
- <NeedsReviewButton dagId={dagId} />
+ <NeedsReviewButton
+ dagId={dagId}
+ refreshInterval={gridRuns?.some((dr) => isStatePending(dr.state)) ? refetchInterval : false}
+ />
<Box my={2}>
<TimeRangeSelector
defaultValue={defaultHour}
diff --git a/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx b/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx
index 3ee154c1392..6eb82b710aa 100644
--- a/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Task/Overview/Overview.tsx
@@ -72,7 +72,12 @@ export const Overview = () => {
return (
<Box m={4} spaceY={4}>
- <NeedsReviewButton taskId={taskId} />
+ <NeedsReviewButton
+ refreshInterval={
+ taskInstances?.task_instances.some((ti) => isStatePending(ti.state)) ? refetchInterval : false
+ }
+ taskId={taskId}
+ />
<Box my={2}>
<TimeRangeSelector
defaultValue={defaultHour}
(airflow) 02/22: Update query client strategy (#55528)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit f87674b12d8897065ce35183a494189added3d77
Author: Pierre Jeambrun <pi...@gmail.com>
AuthorDate: Thu Sep 11 21:06:04 2025 +0200
Update query client strategy (#55528)
(cherry picked from commit ff5d7f6dfbd5b4ba1decfcfec8465c270f222004)
---
airflow-core/src/airflow/ui/src/queryClient.ts | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/queryClient.ts b/airflow-core/src/airflow/ui/src/queryClient.ts
index f090a7c7939..b3147032ace 100644
--- a/airflow-core/src/airflow/ui/src/queryClient.ts
+++ b/airflow-core/src/airflow/ui/src/queryClient.ts
@@ -29,15 +29,13 @@ if (OpenAPI.BASE.endsWith("/")) {
export const client = new QueryClient({
defaultOptions: {
mutations: {
- retry: 1,
- retryDelay: 500,
+ retry: 3,
},
queries: {
initialDataUpdatedAt: new Date().setMinutes(-6), // make sure initial data is already expired
refetchOnMount: true, // Refetches stale queries, not "always"
refetchOnWindowFocus: false,
- retry: 1,
- retryDelay: 500,
+ retry: 3,
staleTime: 5 * 60 * 1000, // 5 minutes
},
},
(airflow) 13/22: feat(hitl): get rid off "Fallback to defaults" in HITL (#55536)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 4b76188ee7d0697375ce6f724c126d7d36dd6416
Author: Wei Lee <we...@gmail.com>
AuthorDate: Fri Sep 12 09:38:42 2025 +0800
feat(hitl): get rid off "Fallback to defaults" in HITL (#55536)
(cherry picked from commit e41de0880640a98a378764c5e5d52c3ee98578fd)
---
airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py | 2 +-
airflow-core/src/airflow/models/hitl.py | 5 -----
.../unit/api_fastapi/execution_api/versions/head/test_hitl.py | 7 ++-----
providers/standard/src/airflow/providers/standard/triggers/hitl.py | 7 ++-----
providers/standard/tests/unit/standard/triggers/test_hitl.py | 2 +-
5 files changed, 6 insertions(+), 17 deletions(-)
diff --git a/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py b/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py
index 8a6606bd280..85d8e008990 100644
--- a/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py
+++ b/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py
@@ -116,7 +116,7 @@ def update_hitl_detail(
f"Human-in-the-loop detail for Task Instance with id {ti_id_str} already exists.",
)
- hitl_detail_model.responded_by = HITLDetail.DEFAULT_USER
+ hitl_detail_model.responded_by = None
hitl_detail_model.response_at = datetime.now(timezone.utc)
hitl_detail_model.chosen_options = payload.chosen_options
hitl_detail_model.params_input = payload.params_input
diff --git a/airflow-core/src/airflow/models/hitl.py b/airflow-core/src/airflow/models/hitl.py
index e2055344bc3..7f0069688c1 100644
--- a/airflow-core/src/airflow/models/hitl.py
+++ b/airflow-core/src/airflow/models/hitl.py
@@ -167,8 +167,3 @@ class HITLDetail(Base):
id=self.responded_by["id"],
name=self.responded_by["name"],
)
-
- DEFAULT_USER = HITLUser(
- id="Fallback to defaults",
- name="Fallback to defaults",
- )
diff --git a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
index 764d5a48ccd..33a1b3e79bc 100644
--- a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
+++ b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
@@ -93,7 +93,7 @@ def expected_sample_hitl_detail_dict(sample_ti: TaskInstance) -> dict[str, Any]:
"params_input": {"input_1": 2},
"response_at": convert_to_utc(datetime(2025, 7, 3, 0, 0, 0)),
"chosen_options": ["Reject"],
- "responded_by": {"id": "Fallback to defaults", "name": "Fallback to defaults"},
+ "responded_by": None,
},
},
],
@@ -174,10 +174,7 @@ def test_update_hitl_detail(client: Client, sample_ti: TaskInstance) -> None:
"response_at": "2025-07-03T00:00:00Z",
"chosen_options": ["Reject"],
"response_received": True,
- "responded_by_user": {
- "id": "Fallback to defaults",
- "name": "Fallback to defaults",
- },
+ "responded_by_user": None,
}
diff --git a/providers/standard/src/airflow/providers/standard/triggers/hitl.py b/providers/standard/src/airflow/providers/standard/triggers/hitl.py
index a9299ae2407..bdaa84b01f6 100644
--- a/providers/standard/src/airflow/providers/standard/triggers/hitl.py
+++ b/providers/standard/src/airflow/providers/standard/triggers/hitl.py
@@ -44,7 +44,7 @@ class HITLTriggerEventSuccessPayload(TypedDict, total=False):
chosen_options: list[str]
params_input: dict[str, Any]
- responded_by_user: HITLUser
+ responded_by_user: HITLUser | None
timedout: bool
@@ -148,10 +148,7 @@ class HITLTrigger(BaseTrigger):
HITLTriggerEventSuccessPayload(
chosen_options=self.defaults,
params_input=self.params,
- responded_by_user=HITLUser(
- id="Fallback to defaults",
- name="Fallback to defaults",
- ),
+ responded_by_user=None,
timedout=True,
)
)
diff --git a/providers/standard/tests/unit/standard/triggers/test_hitl.py b/providers/standard/tests/unit/standard/triggers/test_hitl.py
index ef82f9e09d9..118bcb77dc1 100644
--- a/providers/standard/tests/unit/standard/triggers/test_hitl.py
+++ b/providers/standard/tests/unit/standard/triggers/test_hitl.py
@@ -125,7 +125,7 @@ class TestHITLTrigger:
HITLTriggerEventSuccessPayload(
chosen_options=["1"],
params_input={"input": 1},
- responded_by_user={"id": "Fallback to defaults", "name": "Fallback to defaults"},
+ responded_by_user=None,
timedout=True,
)
)
(airflow) 06/22: fix(ui): Connection Form Extras not inferring the correct type (#55492)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 792cc4fb1ecfec9a4a55e697b92dde70a103ec27
Author: yangyulely <ya...@163.com>
AuthorDate: Fri Sep 12 04:56:35 2025 +0800
fix(ui): Connection Form Extras not inferring the correct type (#55492)
(cherry picked from commit a697436c8358979cbeb68aaeb02e0a1d350d3e15)
---
.../ui/src/components/FlexibleForm/FieldSelector.tsx | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldSelector.tsx b/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldSelector.tsx
index 5580ebaa8a4..7dcd092615d 100644
--- a/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldSelector.tsx
+++ b/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldSelector.tsx
@@ -89,8 +89,18 @@ const isFieldTime = (fieldType: string, fieldSchema: ParamSchema) =>
export const FieldSelector = ({ name, namespace = "default", onUpdate }: FlexibleFormElementProps) => {
// FUTURE: Add support for other types as described in AIP-68 via Plugins
- const { initialParamDict } = useParamStore(namespace);
- const param = initialParamDict[name] ?? paramPlaceholder;
+ const { initialParamDict, paramsDict } = useParamStore(namespace);
+
+ // Use current paramsDict (which has actual values) for type inference, fall back to initialParamDict for schema
+ const currentParam = paramsDict[name];
+ const initialParam = initialParamDict[name] ?? paramPlaceholder;
+
+ // Create a param object that combines the schema from initialParamDict with the value from paramsDict
+ const param: ParamSpec = {
+ ...initialParam,
+ value: currentParam?.value ?? initialParam.value,
+ };
+
const fieldType = inferType(param);
if (isFieldBool(fieldType)) {
(airflow) 10/22: Remove config loader from react tests (#55541)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 44724c2af4979f7eb0ded87f038edbbf06b87850
Author: Brent Bovenzi <br...@astronomer.io>
AuthorDate: Thu Sep 11 15:34:52 2025 -0600
Remove config loader from react tests (#55541)
(cherry picked from commit 0b6c753d3713cae4168d1850990fc0f0f5ca96e7)
---
airflow-core/src/airflow/ui/src/utils/AppWrapper.tsx | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/airflow-core/src/airflow/ui/src/utils/AppWrapper.tsx b/airflow-core/src/airflow/ui/src/utils/AppWrapper.tsx
index 5e5e1c01aa8..6e1799e8cf3 100644
--- a/airflow-core/src/airflow/ui/src/utils/AppWrapper.tsx
+++ b/airflow-core/src/airflow/ui/src/utils/AppWrapper.tsx
@@ -27,7 +27,13 @@ type Props = {
};
export const AppWrapper = ({ initialEntries }: Props) => {
- const router = createMemoryRouter(routerConfig, { basename: "/", initialEntries });
+ // Strip the config loader from tests
+ const config = routerConfig.map((route) => ({
+ ...route,
+ loader: undefined,
+ }));
+
+ const router = createMemoryRouter(config, { basename: "/", initialEntries });
return (
<BaseWrapper>
(airflow) 17/22: Fix Grid cache invalidation for multi-run task operations (#55504)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit dd8690c4feafd45c66e4d170eae2ba48530693df
Author: Gwak Beomgyu <67...@users.noreply.github.com>
AuthorDate: Sat Sep 13 02:08:10 2025 +0900
Fix Grid cache invalidation for multi-run task operations (#55504)
* Fix Grid cache invalidation for multi-run task operations
* made the code simpler
Co-authored-by: Brent Bovenzi <br...@gmail.com>
* Restore variable name
* fix typescript error
---------
Co-authored-by: Brent Bovenzi <br...@gmail.com>
(cherry picked from commit 07dd277985fe64ba32633603d7427fc3a2fa5ac1)
---
.../airflow/ui/src/queries/useClearTaskInstances.ts | 9 ++++++++-
.../airflow/ui/src/queries/usePatchTaskInstance.ts | 19 +++++++++++++++++--
2 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/queries/useClearTaskInstances.ts b/airflow-core/src/airflow/ui/src/queries/useClearTaskInstances.ts
index ed3431bba08..b20dae1075f 100644
--- a/airflow-core/src/airflow/ui/src/queries/useClearTaskInstances.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useClearTaskInstances.ts
@@ -26,6 +26,7 @@ import {
useTaskInstanceServicePostClearTaskInstances,
UseGridServiceGetGridRunsKeyFn,
UseGridServiceGetGridTiSummariesKeyFn,
+ useGridServiceGetGridTiSummariesKey,
} from "openapi/queries";
import type { ClearTaskInstancesBody, TaskInstanceCollectionResponse } from "openapi/requests/types.gen";
import { toaster } from "src/components/ui";
@@ -78,6 +79,10 @@ export const useClearTaskInstances = ({
),
];
+ // Check if this clear operation affects multiple DAG runs
+ const { include_future: includeFuture, include_past: includePast } = variables.requestBody;
+ const affectsMultipleRuns = includeFuture === true || includePast === true;
+
const queryKeys = [
...taskInstanceKeys,
UseDagRunServiceGetDagRunKeyFn({ dagId, dagRunId }),
@@ -85,7 +90,9 @@ export const useClearTaskInstances = ({
[useClearTaskInstancesDryRunKey, dagId],
[usePatchTaskInstanceDryRunKey, dagId, dagRunId],
UseGridServiceGetGridRunsKeyFn({ dagId }, [{ dagId }]),
- UseGridServiceGetGridTiSummariesKeyFn({ dagId, runId: dagRunId }, [{ dagId, runId: dagRunId }]),
+ affectsMultipleRuns
+ ? [useGridServiceGetGridTiSummariesKey, { dagId }]
+ : UseGridServiceGetGridTiSummariesKeyFn({ dagId, runId: dagRunId }),
];
await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({ queryKey: key })));
diff --git a/airflow-core/src/airflow/ui/src/queries/usePatchTaskInstance.ts b/airflow-core/src/airflow/ui/src/queries/usePatchTaskInstance.ts
index bc2747b1a57..c1ae88cacb1 100644
--- a/airflow-core/src/airflow/ui/src/queries/usePatchTaskInstance.ts
+++ b/airflow-core/src/airflow/ui/src/queries/usePatchTaskInstance.ts
@@ -26,6 +26,7 @@ import {
useTaskInstanceServicePatchTaskInstance,
UseGridServiceGetGridRunsKeyFn,
UseGridServiceGetGridTiSummariesKeyFn,
+ useGridServiceGetGridTiSummariesKey,
} from "openapi/queries";
import { toaster } from "src/components/ui";
@@ -58,7 +59,19 @@ export const usePatchTaskInstance = ({
});
};
- const onSuccessFn = async () => {
+ const onSuccessFn = async (
+ _: unknown,
+ variables: {
+ dagId: string;
+ dagRunId: string;
+ requestBody: { include_future?: boolean; include_past?: boolean };
+ taskId: string;
+ },
+ ) => {
+ // Check if this patch operation affects multiple DAG runs
+ const { include_future: includeFuture, include_past: includePast } = variables.requestBody;
+ const affectsMultipleRuns = includeFuture === true || includePast === true;
+
const queryKeys = [
UseTaskInstanceServiceGetTaskInstanceKeyFn({ dagId, dagRunId, taskId }),
UseTaskInstanceServiceGetMappedTaskInstanceKeyFn({ dagId, dagRunId, mapIndex, taskId }),
@@ -66,7 +79,9 @@ export const usePatchTaskInstance = ({
[usePatchTaskInstanceDryRunKey, dagId, dagRunId, { mapIndex, taskId }],
[useClearTaskInstancesDryRunKey, dagId],
UseGridServiceGetGridRunsKeyFn({ dagId }, [{ dagId }]),
- UseGridServiceGetGridTiSummariesKeyFn({ dagId, runId: dagRunId }, [{ dagId, runId: dagRunId }]),
+ affectsMultipleRuns
+ ? [useGridServiceGetGridTiSummariesKey, { dagId }]
+ : UseGridServiceGetGridTiSummariesKeyFn({ dagId, runId: dagRunId }),
];
await Promise.all(queryKeys.map((key) => queryClient.invalidateQueries({ queryKey: key })));
(airflow) 14/22: Remove FAB provider from expected providers for PROD image 3.13 verification (#55523)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 665c69397fd2325a4ab46bcb18ed53f5d209b0d3
Author: Jarek Potiuk <ja...@potiuk.com>
AuthorDate: Fri Sep 12 03:52:46 2025 +0000
Remove FAB provider from expected providers for PROD image 3.13 verification (#55523)
The #55518 assumed that PYTHON_MAJOR_MINOR_VERSION is set, this one changes it
to use python version retrieved from inside of the image.
(cherry picked from commit d4e92e6bb85c55e1394fbe1704c150cfbbcfacbb)
---
.github/workflows/prod-image-build.yml | 4 ++++
docker-tests/tests/docker_tests/test_prod_image.py | 27 +++++++++++++---------
2 files changed, 20 insertions(+), 11 deletions(-)
diff --git a/.github/workflows/prod-image-build.yml b/.github/workflows/prod-image-build.yml
index 292d0d37fe3..93eb251cf0d 100644
--- a/.github/workflows/prod-image-build.yml
+++ b/.github/workflows/prod-image-build.yml
@@ -228,6 +228,10 @@ jobs:
with:
name: prod-packages
path: ./docker-context-files
+ - name: "Remove fab provider for python 3.13"
+ shell: bash
+ run: rm -vf ./docker-context-files/apache_airflow_providers_fab-*.whl
+ if: matrix.python-version == '3.13'
- name: "Show downloaded packages"
run: ls -la ./docker-context-files
- name: "Download constraints"
diff --git a/docker-tests/tests/docker_tests/test_prod_image.py b/docker-tests/tests/docker_tests/test_prod_image.py
index 068f8ca8bbd..0b72c0e03cf 100644
--- a/docker-tests/tests/docker_tests/test_prod_image.py
+++ b/docker-tests/tests/docker_tests/test_prod_image.py
@@ -54,16 +54,6 @@ REGULAR_IMAGE_PROVIDERS = [
if not provider_id.startswith("#")
]
-# Exclude FAB provider for Python 3.13 as it is not yet supported
-py_version = os.environ.get("PYTHON_MAJOR_MINOR_VERSION", "")
-
-if py_version == "3.13":
- if "apache-airflow-providers-fab" in REGULAR_IMAGE_PROVIDERS:
- REGULAR_IMAGE_PROVIDERS.remove("apache-airflow-providers-fab")
-
- if "apache-airflow-providers-fab" in SLIM_IMAGE_PROVIDERS:
- SLIM_IMAGE_PROVIDERS.remove("apache-airflow-providers-fab")
-
testing_slim_image = os.environ.get("TEST_SLIM_IMAGE", False)
@@ -103,6 +93,12 @@ class TestPythonPackages:
else:
packages_to_install = set(REGULAR_IMAGE_PROVIDERS)
assert len(packages_to_install) != 0
+ python_version = run_bash_in_docker(
+ "python --version",
+ image=default_docker_image,
+ )
+ if python_version.startswith("Python 3.13"):
+ packages_to_install.remove("apache-airflow-providers-fab")
output = run_bash_in_docker(
"airflow providers list --output json",
image=default_docker_image,
@@ -204,7 +200,16 @@ class TestPythonPackages:
"package_name,import_names",
SLIM_PACKAGE_IMPORTS.items() if testing_slim_image else REGULAR_PACKAGE_IMPORTS.items(),
)
- def test_check_dependencies_imports(self, package_name, import_names, default_docker_image):
+ def test_check_dependencies_imports(
+ self, package_name: str, import_names: list[str], default_docker_image: str
+ ):
+ if package_name == "providers":
+ python_version = run_bash_in_docker(
+ "python --version",
+ image=default_docker_image,
+ )
+ if python_version.startswith("Python 3.13"):
+ import_names.remove("airflow.providers.fab")
run_python_in_docker(f"import {','.join(import_names)}", image=default_docker_image)
def test_there_is_no_opt_airflow_airflow_folder(self, default_docker_image):
(airflow) 09/22: Bump axios from 1.11.0 to 1.12.0 in /airflow-core/src/airflow/ui (#55551)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit b6fec33bf5cb4d824fefb75d8b47b56875e2a062
Author: Dependabot [bot] <49...@users.noreply.github.com>
AuthorDate: Fri Sep 12 11:12:55 2025 -0400
Bump axios from 1.11.0 to 1.12.0 in /airflow-core/src/airflow/ui (#55551)
Bumps [axios](https://github.com/axios/axios) from 1.11.0 to 1.12.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.11.0...v1.12.0)
---
updated-dependencies:
- dependency-name: axios
dependency-version: 1.12.0
dependency-type: direct:production
...
Signed-off-by: dependabot[bot] <su...@github.com>
Co-authored-by: dependabot[bot] <49...@users.noreply.github.com>
(cherry picked from commit a64e2485e66c28d2ecb108d45414aac37689f193)
---
airflow-core/src/airflow/ui/package.json | 2 +-
airflow-core/src/airflow/ui/pnpm-lock.yaml | 27 ++++++++++++++-------------
2 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/airflow-core/src/airflow/ui/package.json b/airflow-core/src/airflow/ui/package.json
index 79819150481..058c1a5a1de 100644
--- a/airflow-core/src/airflow/ui/package.json
+++ b/airflow-core/src/airflow/ui/package.json
@@ -29,7 +29,7 @@
"@visx/group": "^3.12.0",
"@visx/shape": "^3.12.0",
"@xyflow/react": "^12.4.4",
- "axios": "^1.11.0",
+ "axios": "^1.12.0",
"chakra-react-select": "6.1.0",
"chart.js": "^4.4.9",
"chartjs-adapter-dayjs-4": "^1.0.4",
diff --git a/airflow-core/src/airflow/ui/pnpm-lock.yaml b/airflow-core/src/airflow/ui/pnpm-lock.yaml
index 6f7806f784b..3f0baca129e 100644
--- a/airflow-core/src/airflow/ui/pnpm-lock.yaml
+++ b/airflow-core/src/airflow/ui/pnpm-lock.yaml
@@ -48,8 +48,8 @@ importers:
specifier: ^12.4.4
version: 12.4.4(@types/react@18.3.19)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
axios:
- specifier: ^1.11.0
- version: 1.11.0
+ specifier: ^1.12.0
+ version: 1.12.0
chakra-react-select:
specifier: 6.1.0
version: 6.1.0(@chakra-ui/react@3.20.0(@emotion/react@11.14.0(@types/react@18.3.19)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.19)(next-themes@0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -1840,6 +1840,7 @@ packages:
atlassian-openapi@1.0.21:
resolution: {integrity: sha512-1OnnoY2CQYHgXrce/06BltL7fox+uVY7brHUInyFbMpTURjTNIGXfLQxVDRo/2On7ryyKzkX7FfNApYhXw7f+w==}
+ deprecated: 'DEPRECATED: atlassian-openapi has moved to @atlassian/atlassian-openapi. The latest version is 1.0.6. Please update your dependency.'
available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
@@ -1849,8 +1850,8 @@ packages:
resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==}
engines: {node: '>=4'}
- axios@1.11.0:
- resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==}
+ axios@1.12.0:
+ resolution: {integrity: sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==}
axobject-query@4.1.0:
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
@@ -2614,8 +2615,8 @@ packages:
peerDependencies:
react: ^15.0.2 || ^16.0.0 || ^17.0.0
- follow-redirects@1.15.9:
- resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
+ follow-redirects@1.15.11:
+ resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
@@ -3154,8 +3155,8 @@ packages:
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
- lru-cache@11.1.0:
- resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==}
+ lru-cache@11.2.1:
+ resolution: {integrity: sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==}
engines: {node: 20 || >=22}
lz-string@1.5.0:
@@ -6879,9 +6880,9 @@ snapshots:
axe-core@4.10.3: {}
- axios@1.11.0:
+ axios@1.12.0:
dependencies:
- follow-redirects: 1.15.9
+ follow-redirects: 1.15.11
form-data: 4.0.4
proxy-from-env: 1.1.0
transitivePeerDependencies:
@@ -7834,7 +7835,7 @@ snapshots:
transitivePeerDependencies:
- encoding
- follow-redirects@1.15.9: {}
+ follow-redirects@1.15.11: {}
for-each@0.3.5:
dependencies:
@@ -8394,7 +8395,7 @@ snapshots:
lru-cache@10.4.3: {}
- lru-cache@11.1.0: {}
+ lru-cache@11.2.1: {}
lz-string@1.5.0: {}
@@ -9047,7 +9048,7 @@ snapshots:
path-scurry@2.0.0:
dependencies:
- lru-cache: 11.1.0
+ lru-cache: 11.2.1
minipass: 7.1.2
path-to-regexp@6.3.0: {}
(airflow) 03/22: Fix gantt chart rendering issues (#55554)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 78b28f8c0b05b971f119a692866272decffb9035
Author: Guan Ming(Wesley) Chiu <10...@users.noreply.github.com>
AuthorDate: Sat Sep 13 02:30:06 2025 +0800
Fix gantt chart rendering issues (#55554)
* Update Gantt chart options to enhance animation handling
* Update Gantt chart date range calculation to use data-driven values
(cherry picked from commit 8844d39ad8ac13335f6a1ae9268eb468892567e7)
---
.../airflow/ui/src/layouts/Details/Gantt/utils.ts | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts
index 8d87da1364a..d5f937ab42e 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/utils.ts
@@ -93,7 +93,8 @@ export const createChartOptions = ({
translate,
}: ChartOptionsParams) => ({
animation: {
- duration: 100,
+ duration: 150,
+ easing: "linear" as const,
},
indexAxis: "y" as const,
maintainAspectRatio: false,
@@ -108,7 +109,7 @@ export const createChartOptions = ({
plugins: {
annotation: {
annotations:
- selectedId === undefined
+ selectedId === undefined || selectedId === ""
? []
: [
{
@@ -156,8 +157,21 @@ export const createChartOptions = ({
color: gridColor,
display: true,
},
- max: formatDate(selectedRun?.end_date, selectedTimezone),
- min: formatDate(selectedRun?.start_date, selectedTimezone),
+ max:
+ data.length > 0
+ ? (() => {
+ const maxTime = Math.max(...data.map((item) => new Date(item.x[1] ?? "").getTime()));
+ const minTime = Math.min(...data.map((item) => new Date(item.x[0] ?? "").getTime()));
+ const totalDuration = maxTime - minTime;
+
+ // add 5% to the max time to avoid the last tick being cut off
+ return maxTime + totalDuration * 0.05;
+ })()
+ : formatDate(selectedRun?.end_date, selectedTimezone),
+ min:
+ data.length > 0
+ ? Math.min(...data.map((item) => new Date(item.x[0] ?? "").getTime()))
+ : formatDate(selectedRun?.start_date, selectedTimezone),
position: "top" as const,
stacked: true,
ticks: {
(airflow) 15/22: Unify datetime format in the UI (#55572)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 58735c44ba538ebe3b3e73e08a192b1b76df9905
Author: Pierre Jeambrun <pi...@gmail.com>
AuthorDate: Fri Sep 12 08:35:33 2025 -0600
Unify datetime format in the UI (#55572)
(cherry picked from commit 318a1f162d94941cf038b20c7db19c8a3ee91558)
---
airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx | 3 ++-
airflow-core/src/airflow/ui/src/components/DurationChart.tsx | 3 ++-
airflow-core/src/airflow/ui/src/components/Time.test.tsx | 9 +++++----
airflow-core/src/airflow/ui/src/components/Time.tsx | 9 +++------
.../airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx | 3 ++-
.../src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx | 10 +++++-----
.../src/airflow/ui/src/layouts/Nav/TimezoneSelector.tsx | 3 ++-
.../ui/src/pages/HITLTaskInstances/HITLResponseForm.tsx | 3 ++-
airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts | 5 ++++-
9 files changed, 27 insertions(+), 21 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx b/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx
index d31257a3623..3ee9dbf62c8 100644
--- a/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DateTimeInput.tsx
@@ -22,6 +22,7 @@ import tz from "dayjs/plugin/timezone";
import { forwardRef } from "react";
import { useTimezone } from "src/context/timezone";
+import { DEFAULT_DATETIME_FORMAT } from "src/utils/datetimeUtils";
dayjs.extend(tz);
@@ -35,7 +36,7 @@ export const DateTimeInput = forwardRef<HTMLInputElement, Props>(({ onChange, va
// Convert UTC value to local time for display
const displayValue =
Boolean(value) && dayjs(value).isValid()
- ? dayjs(value).tz(selectedTimezone).format("YYYY-MM-DDTHH:mm:ss.SSS")
+ ? dayjs(value).tz(selectedTimezone).format(DEFAULT_DATETIME_FORMAT)
: "";
return (
diff --git a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx
index e0a44ebe9aa..c0fd7cf796b 100644
--- a/airflow-core/src/airflow/ui/src/components/DurationChart.tsx
+++ b/airflow-core/src/airflow/ui/src/components/DurationChart.tsx
@@ -36,6 +36,7 @@ import { useNavigate } from "react-router-dom";
import type { TaskInstanceResponse, GridRunsResponse } from "openapi/requests/types.gen";
import { getComputedCSSVariableValue } from "src/theme";
+import { DEFAULT_DATETIME_FORMAT } from "src/utils/datetimeUtils";
ChartJS.register(
CategoryScale,
@@ -160,7 +161,7 @@ export const DurationChart = ({
label: translate("durationChart.runDuration"),
},
],
- labels: entries.map((entry: RunResponse) => dayjs(entry.run_after).format("YYYY-MM-DD, hh:mm:ss")),
+ labels: entries.map((entry: RunResponse) => dayjs(entry.run_after).format(DEFAULT_DATETIME_FORMAT)),
}}
datasetIdKey="id"
options={{
diff --git a/airflow-core/src/airflow/ui/src/components/Time.test.tsx b/airflow-core/src/airflow/ui/src/components/Time.test.tsx
index c2f0f53c02c..f342ad0b484 100644
--- a/airflow-core/src/airflow/ui/src/components/Time.test.tsx
+++ b/airflow-core/src/airflow/ui/src/components/Time.test.tsx
@@ -22,8 +22,9 @@ import { describe, it, expect, vi } from "vitest";
import { TimezoneContext } from "src/context/timezone";
import { Wrapper } from "src/utils/Wrapper";
+import { DEFAULT_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT_WITH_TZ } from "src/utils/datetimeUtils";
-import Time, { defaultFormat, defaultFormatWithTZ } from "./Time";
+import Time from "./Time";
describe("Test Time and TimezoneProvider", () => {
it("Displays a UTC time correctly", () => {
@@ -38,7 +39,7 @@ describe("Test Time and TimezoneProvider", () => {
},
);
- const utcTime = screen.getByText(dayjs.utc(now).format(defaultFormat));
+ const utcTime = screen.getByText(dayjs.utc(now).format(DEFAULT_DATETIME_FORMAT));
expect(utcTime).toBeDefined();
expect(utcTime.title).toBeFalsy();
@@ -58,9 +59,9 @@ describe("Test Time and TimezoneProvider", () => {
);
const nowTime = dayjs(now);
- const samoaTime = screen.getByText(nowTime.tz(tz).format(defaultFormat));
+ const samoaTime = screen.getByText(nowTime.tz(tz).format(DEFAULT_DATETIME_FORMAT));
expect(samoaTime).toBeDefined();
- expect(samoaTime.title).toEqual(nowTime.tz("UTC").format(defaultFormatWithTZ));
+ expect(samoaTime.title).toEqual(nowTime.tz("UTC").format(DEFAULT_DATETIME_FORMAT_WITH_TZ));
});
});
diff --git a/airflow-core/src/airflow/ui/src/components/Time.tsx b/airflow-core/src/airflow/ui/src/components/Time.tsx
index 8edd92d983f..99b7f029f72 100644
--- a/airflow-core/src/airflow/ui/src/components/Time.tsx
+++ b/airflow-core/src/airflow/ui/src/components/Time.tsx
@@ -23,10 +23,7 @@ import tz from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import { useTimezone } from "src/context/timezone";
-
-export const defaultFormat = "YYYY-MM-DD, HH:mm:ss";
-export const defaultFormatWithTZ = `${defaultFormat} z`;
-export const defaultTZFormat = "z (Z)";
+import { DEFAULT_DATETIME_FORMAT, DEFAULT_DATETIME_FORMAT_WITH_TZ } from "src/utils/datetimeUtils";
dayjs.extend(utc);
dayjs.extend(tz);
@@ -38,7 +35,7 @@ type Props = {
readonly showTooltip?: boolean;
} & SpanProps;
-const Time = ({ datetime, format = defaultFormat, showTooltip = true, ...rest }: Props) => {
+const Time = ({ datetime, format = DEFAULT_DATETIME_FORMAT, showTooltip = true, ...rest }: Props) => {
const { selectedTimezone } = useTimezone();
const time = dayjs(datetime);
@@ -47,7 +44,7 @@ const Time = ({ datetime, format = defaultFormat, showTooltip = true, ...rest }:
}
const formattedTime = time.tz(selectedTimezone).format(format);
- const utcTime = time.tz("UTC").format(defaultFormatWithTZ);
+ const utcTime = time.tz("UTC").format(DEFAULT_DATETIME_FORMAT_WITH_TZ);
return (
<chakra.span dir="ltr" {...rest}>
diff --git a/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx b/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx
index 8bd3484ca33..8c92730714d 100644
--- a/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx
+++ b/airflow-core/src/airflow/ui/src/components/TriggerDag/TriggerDAGForm.tsx
@@ -27,6 +27,7 @@ import { useDagParams } from "src/queries/useDagParams";
import { useParamStore } from "src/queries/useParamStore";
import { useTogglePause } from "src/queries/useTogglePause";
import { useTrigger } from "src/queries/useTrigger";
+import { DEFAULT_DATETIME_FORMAT } from "src/utils/datetimeUtils";
import ConfigForm from "../ConfigForm";
import { DateTimeInput } from "../DateTimeInput";
@@ -65,7 +66,7 @@ const TriggerDAGForm = ({ dagDisplayName, dagId, isPaused, onClose, open }: Trig
conf,
dagRunId: "",
// Default logical date to now, show it in the selected timezone
- logicalDate: dayjs().format("YYYY-MM-DDTHH:mm:ss.SSS"),
+ logicalDate: dayjs().format(DEFAULT_DATETIME_FORMAT),
note: "",
},
});
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx
index fe623727377..655e86e19ce 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Gantt/Gantt.tsx
@@ -48,7 +48,7 @@ import { useGridStructure } from "src/queries/useGridStructure";
import { useGridTiSummaries } from "src/queries/useGridTISummaries";
import { getComputedCSSVariableValue } from "src/theme";
import { isStatePending, useAutoRefresh } from "src/utils";
-import { formatDate } from "src/utils/datetimeUtils";
+import { DEFAULT_DATETIME_FORMAT, formatDate } from "src/utils/datetimeUtils";
import { createHandleBarClick, createChartOptions } from "./utils";
@@ -147,8 +147,8 @@ export const Gantt = ({ limit }: Props) => {
state: gridSummary.state,
taskId: gridSummary.task_id,
x: [
- formatDate(gridSummary.min_start_date, selectedTimezone, "YYYY-MM-DD HH:mm:ss.SSS"),
- formatDate(gridSummary.max_end_date, selectedTimezone, "YYYY-MM-DD HH:mm:ss.SSS"),
+ formatDate(gridSummary.min_start_date, selectedTimezone, DEFAULT_DATETIME_FORMAT),
+ formatDate(gridSummary.max_end_date, selectedTimezone, DEFAULT_DATETIME_FORMAT),
],
y: gridSummary.task_id,
};
@@ -163,8 +163,8 @@ export const Gantt = ({ limit }: Props) => {
state: taskInstance.state,
taskId: taskInstance.task_id,
x: [
- formatDate(taskInstance.start_date, selectedTimezone, "YYYY-MM-DD HH:mm:ss.SSS"),
- formatDate(taskInstance.end_date, selectedTimezone, "YYYY-MM-DD HH:mm:ss.SSS"),
+ formatDate(taskInstance.start_date, selectedTimezone, DEFAULT_DATETIME_FORMAT),
+ formatDate(taskInstance.end_date, selectedTimezone, DEFAULT_DATETIME_FORMAT),
],
y: taskInstance.task_id,
};
diff --git a/airflow-core/src/airflow/ui/src/layouts/Nav/TimezoneSelector.tsx b/airflow-core/src/airflow/ui/src/layouts/Nav/TimezoneSelector.tsx
index 7a458f668c3..1ab91744cb3 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Nav/TimezoneSelector.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Nav/TimezoneSelector.tsx
@@ -25,6 +25,7 @@ import React, { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useTimezone } from "src/context/timezone";
+import { DEFAULT_DATETIME_FORMAT } from "src/utils/datetimeUtils";
import type { Option as TimezoneOption } from "src/utils/option";
dayjs.extend(utc);
@@ -60,7 +61,7 @@ const TimezoneSelector: React.FC = () => {
useEffect(() => {
const updateTime = () => {
- setCurrentTime(dayjs().tz(selectedTimezone).format("YYYY-MM-DD HH:mm:ss"));
+ setCurrentTime(dayjs().tz(selectedTimezone).format(DEFAULT_DATETIME_FORMAT));
};
updateTime();
diff --git a/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLResponseForm.tsx b/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLResponseForm.tsx
index 30610756121..e703683ce9b 100644
--- a/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLResponseForm.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLResponseForm.tsx
@@ -27,6 +27,7 @@ import { FlexibleForm } from "src/components/FlexibleForm/FlexibleForm";
import Time from "src/components/Time";
import { useParamStore } from "src/queries/useParamStore";
import { useUpdateHITLDetail } from "src/queries/useUpdateHITLDetail";
+import { DEFAULT_DATETIME_FORMAT } from "src/utils/datetimeUtils";
import { getHITLParamsDict, getHITLFormData, getPreloadHITLFormData } from "src/utils/hitl";
type HITLResponseFormProps = {
@@ -97,7 +98,7 @@ export const HITLResponseForm = ({ hitlDetail }: HITLResponseFormProps) => {
{hitlDetail.response_received ? (
<Text color="fg.muted" fontSize="sm">
{translate("response.received")}
- <Time datetime={hitlDetail.response_at} format="YYYY-MM-DD, HH:mm:ss" />
+ <Time datetime={hitlDetail.response_at} format={DEFAULT_DATETIME_FORMAT} />
</Text>
) : undefined}
<Accordion.Root
diff --git a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts
index bcc1f657316..f1871b4d4fe 100644
--- a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts
+++ b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts
@@ -23,6 +23,9 @@ import tz from "dayjs/plugin/timezone";
dayjs.extend(dayjsDuration);
dayjs.extend(tz);
+export const DEFAULT_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
+export const DEFAULT_DATETIME_FORMAT_WITH_TZ = `${DEFAULT_DATETIME_FORMAT} z`;
+
export const renderDuration = (durationSeconds: number | null | undefined): string => {
if (
durationSeconds === null ||
@@ -51,7 +54,7 @@ export const getDuration = (startDate?: string | null, endDate?: string | null)
export const formatDate = (
date: number | string | null | undefined,
timezone: string,
- format: string = "YYYY-MM-DD HH:mm:ss",
+ format: string = DEFAULT_DATETIME_FORMAT,
) => {
if (date === null || date === undefined || !dayjs(date).isValid()) {
return dayjs().tz(timezone).format(format);
(airflow) 01/22: Use FilterBar for Asset Event filters (#55522)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit e20ee338072cf1ff87e969fd94c881c7b8fae969
Author: Brent Bovenzi <br...@astronomer.io>
AuthorDate: Thu Sep 11 12:44:32 2025 -0600
Use FilterBar for Asset Event filters (#55522)
(cherry picked from commit 1766a9e0d9a42707c4af728f90429c28640efe87)
---
.../ui/src/components/Assets/AssetEventsFilter.tsx | 107 +++++----------------
.../src/airflow/ui/src/constants/filterConfigs.tsx | 10 ++
.../src/airflow/ui/src/utils/useFiltersHandler.ts | 2 +
3 files changed, 36 insertions(+), 83 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/components/Assets/AssetEventsFilter.tsx b/airflow-core/src/airflow/ui/src/components/Assets/AssetEventsFilter.tsx
index bfa46c9b7df..ceda8443c08 100644
--- a/airflow-core/src/airflow/ui/src/components/Assets/AssetEventsFilter.tsx
+++ b/airflow-core/src/airflow/ui/src/components/Assets/AssetEventsFilter.tsx
@@ -16,96 +16,37 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { VStack, HStack, Box, Text, Button } from "@chakra-ui/react";
-import { useCallback, useMemo, useState } from "react";
-import { useTranslation } from "react-i18next";
-import { LuX } from "react-icons/lu";
-import { useSearchParams } from "react-router-dom";
+import { useMemo } from "react";
-import { DateTimeInput } from "src/components/DateTimeInput";
-import { SearchBar } from "src/components/SearchBar";
+import { FilterBar } from "src/components/FilterBar";
import { SearchParamsKeys } from "src/constants/searchParams";
+import { useFiltersHandler, type FilterableSearchParamsKeys } from "src/utils/useFiltersHandler";
-const { DAG_ID, END_DATE, START_DATE, TASK_ID } = SearchParamsKeys;
-const filterKeys = [START_DATE, END_DATE, DAG_ID, TASK_ID];
+const FILTER_KEYS: Array<FilterableSearchParamsKeys> = [
+ SearchParamsKeys.START_DATE,
+ SearchParamsKeys.END_DATE,
+ SearchParamsKeys.DAG_ID,
+ SearchParamsKeys.TASK_ID,
+];
export const AssetEventsFilter = () => {
- const { t: translate } = useTranslation("common");
- const [searchParams, setSearchParams] = useSearchParams();
- const startDate = searchParams.get(START_DATE) ?? "";
- const endDate = searchParams.get(END_DATE) ?? "";
- const dagId = searchParams.get(DAG_ID) ?? "";
- const taskId = searchParams.get(TASK_ID) ?? "";
- const [resetKey, setResetKey] = useState(0);
- const handleFilterChange = useCallback(
- (paramKey: string) => (value: string) => {
- if (value === "") {
- searchParams.delete(paramKey);
- } else {
- searchParams.set(paramKey, value);
+ const { filterConfigs, handleFiltersChange, searchParams } = useFiltersHandler(FILTER_KEYS);
+
+ const initialValues = useMemo(() => {
+ const values: Record<string, string> = {};
+
+ FILTER_KEYS.forEach((key) => {
+ const value = searchParams.get(key);
+
+ if (value !== null && value.trim() !== "") {
+ values[key] = value;
}
- setSearchParams(searchParams);
- },
- [searchParams, setSearchParams],
- );
- const filterCount = useMemo(
- () => filterKeys.reduce((acc, key) => (searchParams.get(key) === null ? acc : acc + 1), 0),
- [searchParams],
- );
- const handleResetFilters = useCallback(() => {
- filterKeys.forEach((key) => searchParams.delete(key));
- setSearchParams(searchParams);
- setResetKey((prev) => prev + 1);
- }, [searchParams, setSearchParams]);
+ });
+
+ return values;
+ }, [searchParams]);
return (
- <VStack align="start" gap={4} paddingY="4px">
- <HStack flexWrap="wrap" gap={4}>
- <Box w="200px">
- <Text fontSize="xs">{translate("common:table.from")}</Text>
- <DateTimeInput
- onChange={(event) => handleFilterChange(START_DATE)(event.target.value)}
- value={startDate}
- />
- </Box>
- <Box w="200px">
- <Text fontSize="xs">{translate("common:table.to")}</Text>
- <DateTimeInput
- onChange={(event) => handleFilterChange(END_DATE)(event.target.value)}
- value={endDate}
- />
- </Box>
- <Box w="200px">
- <Text fontSize="xs">{translate("common:filters.dagDisplayNamePlaceholder")}</Text>
- <SearchBar
- defaultValue={dagId}
- hideAdvanced
- hotkeyDisabled={true}
- key={`dag-id-${resetKey}`}
- onChange={handleFilterChange(DAG_ID)}
- placeHolder={translate("common:filters.dagDisplayNamePlaceholder")}
- />
- </Box>
- <Box w="200px">
- <Text fontSize="xs">{translate("common:filters.taskIdPlaceholder")}</Text>
- <SearchBar
- defaultValue={taskId}
- hideAdvanced
- hotkeyDisabled={true}
- key={`task-id-${resetKey}`}
- onChange={handleFilterChange(TASK_ID)}
- placeHolder={translate("common:filters.taskIdPlaceholder")}
- />
- </Box>
- <Box alignSelf="end">
- {filterCount > 0 && (
- <Button onClick={handleResetFilters} size="md" variant="outline">
- <LuX />
- {translate("common:table.filterReset", { count: filterCount })}
- </Button>
- )}
- </Box>
- </HStack>
- </VStack>
+ <FilterBar configs={filterConfigs} initialValues={initialValues} onFiltersChange={handleFiltersChange} />
);
};
diff --git a/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx b/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx
index 5bf8c74a832..0dc09b1fd55 100644
--- a/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx
+++ b/airflow-core/src/airflow/ui/src/constants/filterConfigs.tsx
@@ -59,6 +59,11 @@ export const useFilterConfigs = () => {
label: translate("common:dagId"),
type: FilterTypes.TEXT,
},
+ [SearchParamsKeys.END_DATE]: {
+ icon: <MdDateRange />,
+ label: translate("common:table.to"),
+ type: FilterTypes.DATE,
+ },
[SearchParamsKeys.EVENT_TYPE]: {
label: translate("browse:auditLog.filters.eventType"),
type: FilterTypes.TEXT,
@@ -106,6 +111,11 @@ export const useFilterConfigs = () => {
label: translate("common:runId"),
type: FilterTypes.TEXT,
},
+ [SearchParamsKeys.START_DATE]: {
+ icon: <MdDateRange />,
+ label: translate("common:table.from"),
+ type: FilterTypes.DATE,
+ },
[SearchParamsKeys.TASK_ID]: {
hotkeyDisabled: true,
icon: <TaskIcon />,
diff --git a/airflow-core/src/airflow/ui/src/utils/useFiltersHandler.ts b/airflow-core/src/airflow/ui/src/utils/useFiltersHandler.ts
index b6b16fe850a..241a6e9287a 100644
--- a/airflow-core/src/airflow/ui/src/utils/useFiltersHandler.ts
+++ b/airflow-core/src/airflow/ui/src/utils/useFiltersHandler.ts
@@ -29,6 +29,7 @@ export type FilterableSearchParamsKeys =
| SearchParamsKeys.BEFORE
| SearchParamsKeys.DAG_DISPLAY_NAME_PATTERN
| SearchParamsKeys.DAG_ID
+ | SearchParamsKeys.END_DATE
| SearchParamsKeys.EVENT_TYPE
| SearchParamsKeys.KEY_PATTERN
| SearchParamsKeys.LOGICAL_DATE_GTE
@@ -38,6 +39,7 @@ export type FilterableSearchParamsKeys =
| SearchParamsKeys.RUN_AFTER_LTE
| SearchParamsKeys.RUN_ID
| SearchParamsKeys.RUN_ID_PATTERN
+ | SearchParamsKeys.START_DATE
| SearchParamsKeys.TASK_ID
| SearchParamsKeys.TASK_ID_PATTERN
| SearchParamsKeys.TRY_NUMBER
(airflow) 12/22: Add hitl required action count (#55546)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit ce5dc197fc72bf3b803346012be45b6c1dab0f24
Author: Brent Bovenzi <br...@astronomer.io>
AuthorDate: Thu Sep 11 17:22:52 2025 -0600
Add hitl required action count (#55546)
* Add pendign actions count to dag tabs
* Add label to select
(cherry picked from commit 46da5ec33ad355e7699fec3a10138932b3722059)
---
.../airflow/ui/src/hooks/useRequiredActionTabs.ts | 127 +++++++++++++++++++++
airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx | 45 +++-----
.../pages/HITLTaskInstances/HITLTaskInstances.tsx | 1 +
airflow-core/src/airflow/ui/src/pages/Run/Run.tsx | 26 +----
.../src/airflow/ui/src/pages/Task/Task.tsx | 21 ++--
.../ui/src/pages/TaskInstance/TaskInstance.tsx | 44 ++-----
6 files changed, 165 insertions(+), 99 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/hooks/useRequiredActionTabs.ts b/airflow-core/src/airflow/ui/src/hooks/useRequiredActionTabs.ts
new file mode 100644
index 00000000000..ddef7c64b08
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/hooks/useRequiredActionTabs.ts
@@ -0,0 +1,127 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { useEffect } from "react";
+import { useTranslation } from "react-i18next";
+import { useLocation, useNavigate } from "react-router-dom";
+
+import { useHumanInTheLoopServiceGetHitlDetails } from "openapi/queries";
+
+export type HITLQueryParams = {
+ dagId: string;
+ dagRunId?: string;
+ taskId?: string;
+ taskIdPattern?: string;
+};
+
+export type TabItem = {
+ icon: React.ReactNode;
+ label: string;
+ value: string;
+};
+
+export type UseRequiredActionTabsOptions = {
+ autoRedirect?: boolean;
+ refetchInterval?: number | false;
+};
+
+export const useRequiredActionTabs = (
+ hitlParams: HITLQueryParams,
+ tabs: Array<TabItem>,
+ options: UseRequiredActionTabsOptions = {},
+) => {
+ const { t: translate } = useTranslation("hitl");
+ const { autoRedirect = false, refetchInterval } = options;
+ const location = useLocation();
+ const navigate = useNavigate();
+
+ const redirectPath = (() => {
+ const { dagId, dagRunId, taskId, taskIdPattern } = hitlParams;
+
+ if (Boolean(dagId) && Boolean(dagRunId) && Boolean(taskId)) {
+ return `/dags/${dagId}/runs/${dagRunId}/tasks/${taskId}`;
+ }
+ if (Boolean(dagId) && Boolean(dagRunId)) {
+ return `/dags/${dagId}/runs/${dagRunId}`;
+ }
+ if (Boolean(dagId) && Boolean(taskIdPattern)) {
+ return `/dags/${dagId}/tasks/group/${taskIdPattern}`;
+ }
+ if (Boolean(dagId)) {
+ return `/dags/${dagId}`;
+ }
+
+ // Fallback: remove /required_actions from current path
+ return location.pathname.replace("/required_actions", "");
+ })();
+
+ const { data: hitlData, isLoading: isLoadingHitl } = useHumanInTheLoopServiceGetHitlDetails(
+ {
+ dagId: hitlParams.dagId,
+ dagRunId: hitlParams.dagRunId,
+ taskId: hitlParams.taskId,
+ taskIdPattern: hitlParams.taskIdPattern,
+ },
+ undefined,
+ {
+ enabled: Boolean(hitlParams.dagId),
+ refetchInterval,
+ },
+ );
+
+ const hasHitlData = (hitlData?.total_entries ?? 0) > 0;
+ const pendingActionsCount =
+ hitlData?.hitl_details.filter(
+ (hitl) => hitl.task_instance.state === "deferred" && !hitl.response_received,
+ ).length ?? 0;
+
+ const processedTabs = tabs
+ .filter((tab) => {
+ // Hide required_actions tab if no HITL data exists
+ if (tab.value === "required_actions" && !hasHitlData) {
+ return false;
+ }
+
+ return true;
+ })
+ .map((tab) => {
+ // Update required_actions label with pending count
+ if (tab.value === "required_actions" && pendingActionsCount > 0) {
+ return {
+ ...tab,
+ label: translate("requiredActionCount", { count: pendingActionsCount }),
+ };
+ }
+
+ return tab;
+ });
+
+ useEffect(() => {
+ if (autoRedirect && !hasHitlData && !isLoadingHitl && location.pathname.includes("required_actions")) {
+ navigate(redirectPath);
+ }
+ }, [autoRedirect, hasHitlData, isLoadingHitl, location.pathname, navigate, redirectPath]);
+
+ return {
+ hasHitlData,
+ hitlData,
+ isLoadingHitl,
+ pendingActionsCount,
+ tabs: processedTabs,
+ };
+};
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
index 063841c4510..ffcc37b4fb3 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Dag.tsx
@@ -25,13 +25,10 @@ import { MdDetails, MdOutlineEventNote } from "react-icons/md";
import { RiArrowGoBackFill } from "react-icons/ri";
import { useParams } from "react-router-dom";
-import {
- useDagServiceGetDagDetails,
- useDagServiceGetLatestRunInfo,
- useHumanInTheLoopServiceGetHitlDetails,
-} from "openapi/queries";
+import { useDagServiceGetDagDetails, useDagServiceGetLatestRunInfo } from "openapi/queries";
import { TaskIcon } from "src/assets/TaskIcon";
import { usePluginTabs } from "src/hooks/usePluginTabs";
+import { useRequiredActionTabs } from "src/hooks/useRequiredActionTabs";
import { DetailsLayout } from "src/layouts/Details/DetailsLayout";
import { useRefreshOnNewDagRuns } from "src/queries/useRefreshOnNewDagRuns";
import { isStatePending, useAutoRefresh } from "src/utils";
@@ -39,7 +36,7 @@ import { isStatePending, useAutoRefresh } from "src/utils";
import { Header } from "./Header";
export const Dag = () => {
- const { t: translate } = useTranslation("dag");
+ const { t: translate } = useTranslation(["dag", "hitl"]);
const { dagId = "" } = useParams();
// Get external views with dag destination
@@ -73,30 +70,6 @@ export const Dag = () => {
// pending state and new runs are initiated from other page
useRefreshOnNewDagRuns(dagId, hasPendingRuns);
- const { data: hitlData } = useHumanInTheLoopServiceGetHitlDetails(
- {
- dagId,
- },
- undefined,
- {
- enabled: Boolean(dagId),
- },
- );
-
- const hasHitlTaskInstances = (hitlData?.total_entries ?? 0) > 0;
-
- const displayTabs = tabs.filter((tab) => {
- if (dag?.timetable_summary === null && tab.value === "backfills") {
- return false;
- }
-
- if (tab.value === "required_actions" && !hasHitlTaskInstances) {
- return false;
- }
-
- return true;
- });
-
const {
data: latestRun,
error: runsError,
@@ -118,6 +91,18 @@ export const Dag = () => {
},
);
+ const { tabs: processedTabs } = useRequiredActionTabs({ dagId }, tabs, {
+ refetchInterval: isStatePending(latestRun?.state) ? refetchInterval : false,
+ });
+
+ const displayTabs = processedTabs.filter((tab) => {
+ if (dag?.timetable_summary === null && tab.value === "backfills") {
+ return false;
+ }
+
+ return true;
+ });
+
return (
<ReactFlowProvider>
<DetailsLayout error={error ?? runsError} isLoading={isLoading || isLoadingRuns} tabs={displayTabs}>
diff --git a/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLTaskInstances.tsx b/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLTaskInstances.tsx
index 80481a86e9f..663b4615f29 100644
--- a/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLTaskInstances.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLTaskInstances.tsx
@@ -174,6 +174,7 @@ export const HITLTaskInstances = () => {
onValueChange={handleResponseChange}
value={[responseReceived ?? "all"]}
>
+ <Select.Label fontSize="xs">{translate("requiredActionState")}</Select.Label>
<Select.Trigger isActive={Boolean(responseReceived)}>
<Select.ValueText />
</Select.Trigger>
diff --git a/airflow-core/src/airflow/ui/src/pages/Run/Run.tsx b/airflow-core/src/airflow/ui/src/pages/Run/Run.tsx
index d37db572979..eb97b8bf5c9 100644
--- a/airflow-core/src/airflow/ui/src/pages/Run/Run.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Run/Run.tsx
@@ -22,15 +22,16 @@ import { FiCode, FiDatabase, FiUser } from "react-icons/fi";
import { MdDetails, MdOutlineEventNote, MdOutlineTask } from "react-icons/md";
import { useParams } from "react-router-dom";
-import { useDagRunServiceGetDagRun, useHumanInTheLoopServiceGetHitlDetails } from "openapi/queries";
+import { useDagRunServiceGetDagRun } from "openapi/queries";
import { usePluginTabs } from "src/hooks/usePluginTabs";
+import { useRequiredActionTabs } from "src/hooks/useRequiredActionTabs";
import { DetailsLayout } from "src/layouts/Details/DetailsLayout";
import { isStatePending, useAutoRefresh } from "src/utils";
import { Header } from "./Header";
export const Run = () => {
- const { t: translate } = useTranslation("dag");
+ const { t: translate } = useTranslation(["dag", "hitl"]);
const { dagId = "", runId = "" } = useParams();
// Get external views with dag_run destination
@@ -63,25 +64,8 @@ export const Run = () => {
},
);
- const { data: hitlData } = useHumanInTheLoopServiceGetHitlDetails(
- {
- dagId,
- dagRunId: runId,
- },
- undefined,
- {
- enabled: Boolean(dagId && runId),
- },
- );
-
- const hasHitlTasksForRun = Boolean(hitlData?.hitl_details.length);
-
- const displayTabs = tabs.filter((tab) => {
- if (tab.value === "required_actions" && !hasHitlTasksForRun) {
- return false;
- }
-
- return true;
+ const { tabs: displayTabs } = useRequiredActionTabs({ dagId, dagRunId: runId }, tabs, {
+ refetchInterval: isStatePending(dagRun?.state) ? refetchInterval : false,
});
return (
diff --git a/airflow-core/src/airflow/ui/src/pages/Task/Task.tsx b/airflow-core/src/airflow/ui/src/pages/Task/Task.tsx
index cfeb856d82d..6fff8ab723e 100644
--- a/airflow-core/src/airflow/ui/src/pages/Task/Task.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Task/Task.tsx
@@ -23,8 +23,9 @@ import { LuChartColumn } from "react-icons/lu";
import { MdOutlineEventNote, MdOutlineTask } from "react-icons/md";
import { useParams } from "react-router-dom";
-import { useTaskServiceGetTask, useHumanInTheLoopServiceGetHitlDetails } from "openapi/queries";
+import { useTaskServiceGetTask } from "openapi/queries";
import { usePluginTabs } from "src/hooks/usePluginTabs";
+import { useRequiredActionTabs } from "src/hooks/useRequiredActionTabs";
import { DetailsLayout } from "src/layouts/Details/DetailsLayout";
import { useGridStructure } from "src/queries/useGridStructure.ts";
import { getGroupTask } from "src/utils/groupTask";
@@ -33,7 +34,7 @@ import { GroupTaskHeader } from "./GroupTaskHeader";
import { Header } from "./Header";
export const Task = () => {
- const { t: translate } = useTranslation("dag");
+ const { t: translate } = useTranslation(["dag", "hitl"]);
const { dagId = "", groupId, runId, taskId } = useParams();
// Get external views with task destination
@@ -59,24 +60,20 @@ export const Task = () => {
const groupTask = getGroupTask(dagStructure, groupId);
- const { data: hitlData } = useHumanInTheLoopServiceGetHitlDetails(
+ // Handle required action tabs with shared utility
+ const { tabs: processedTabs } = useRequiredActionTabs(
{
dagId,
dagRunId: runId,
taskId: Boolean(groupId) ? undefined : taskId,
taskIdPattern: groupId,
},
- undefined,
- {
- enabled: Boolean(dagId && (groupId !== undefined || taskId !== undefined)),
- },
+ tabs,
);
- const hasHitlForTask = (hitlData?.total_entries ?? 0) > 0;
-
- const displayTabs = (groupId === undefined ? tabs : tabs.filter((tab) => tab.value !== "events")).filter(
- (tab) => tab.value !== "required_actions" || hasHitlForTask,
- );
+ // Filter out events tab for group tasks
+ const displayTabs =
+ groupId === undefined ? processedTabs : processedTabs.filter((tab) => tab.value !== "events");
return (
<ReactFlowProvider>
diff --git a/airflow-core/src/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx b/airflow-core/src/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx
index 39733cb140e..0eb684f0d89 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstance/TaskInstance.tsx
@@ -17,18 +17,16 @@
* under the License.
*/
import { ReactFlowProvider } from "@xyflow/react";
-import { useEffect, useMemo } from "react";
+import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { FiCode, FiDatabase, FiUser } from "react-icons/fi";
import { MdDetails, MdOutlineEventNote, MdOutlineTask, MdReorder, MdSyncAlt } from "react-icons/md";
import { PiBracketsCurlyBold } from "react-icons/pi";
-import { useLocation, useNavigate, useParams } from "react-router-dom";
+import { useParams } from "react-router-dom";
-import {
- useHumanInTheLoopServiceGetHitlDetails,
- useTaskInstanceServiceGetMappedTaskInstance,
-} from "openapi/queries";
+import { useTaskInstanceServiceGetMappedTaskInstance } from "openapi/queries";
import { usePluginTabs } from "src/hooks/usePluginTabs";
+import { useRequiredActionTabs } from "src/hooks/useRequiredActionTabs";
import { DetailsLayout } from "src/layouts/Details/DetailsLayout";
import { useGridTiSummaries } from "src/queries/useGridTISummaries.ts";
import { isStatePending, useAutoRefresh } from "src/utils";
@@ -36,10 +34,8 @@ import { isStatePending, useAutoRefresh } from "src/utils";
import { Header } from "./Header";
export const TaskInstance = () => {
- const { t: translate } = useTranslation("dag");
+ const { t: translate } = useTranslation(["dag", "hitl"]);
const { dagId = "", mapIndex = "-1", runId = "", taskId = "" } = useParams();
- const navigate = useNavigate();
- const location = useLocation();
// Get external views with task_instance destination
const externalTabs = usePluginTabs("task_instance");
@@ -80,21 +76,6 @@ export const TaskInstance = () => {
const { data: gridTISummaries } = useGridTiSummaries({ dagId, runId });
- const { data: hitlDetails, isLoading: isLoadingHitl } = useHumanInTheLoopServiceGetHitlDetails(
- {
- dagId,
- dagRunId: runId,
- taskId,
- },
- undefined,
- {
- enabled: Boolean(dagId && runId),
- refetchInterval,
- },
- );
-
- const hasHitlForTask = (hitlDetails?.total_entries ?? 0) > 0;
-
const taskInstanceSummary = gridTISummaries?.task_instances.find((ti) => ti.task_id === taskId);
const taskCount = useMemo(
() =>
@@ -119,20 +100,11 @@ export const TaskInstance = () => {
];
}
- const displayTabs = newTabs.filter((tab) => {
- if (tab.value === "required_actions" && !hasHitlForTask) {
- return false;
- }
-
- return true;
+ const { tabs: displayTabs } = useRequiredActionTabs({ dagId, dagRunId: runId, taskId }, newTabs, {
+ autoRedirect: true,
+ refetchInterval: isStatePending(taskInstance?.state) ? refetchInterval : false,
});
- useEffect(() => {
- if (!hasHitlForTask && !isLoadingHitl && location.pathname.includes("required_actions")) {
- navigate(`/dags/${dagId}/runs/${runId}/tasks/${taskId}`);
- }
- }, [dagId, error, hasHitlForTask, isLoadingHitl, location.pathname, navigate, runId, taskId]);
-
return (
<ReactFlowProvider>
<DetailsLayout error={error} isLoading={isLoading} tabs={displayTabs}>
(airflow) 05/22: Be a little clearer when task likely OOM error (#55602)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 1958bcc05d810beff0fa715b55d5d07aa3bfdd8c
Author: Daniel Standish <15...@users.noreply.github.com>
AuthorDate: Sat Sep 13 19:56:06 2025 -0700
Be a little clearer when task likely OOM error (#55602)
* Be a little clearer when task likely OOM error
When a task process is terminated with exit code -9 (sigkill) then there's a good chance it's due to out of memory error (OOM). We can be a little more direct about indicating that in the logs. And I reorganize the way we present that in the docs a bit.
* fix test
(cherry picked from commit e3f42b7f656fec43dcd51c76f700724df14920b3)
---
airflow-core/docs/troubleshooting.rst | 22 ++++++++++++++++------
.../src/airflow/sdk/execution_time/supervisor.py | 5 +++--
.../task_sdk/execution_time/test_supervisor.py | 16 ++++++++++++----
3 files changed, 31 insertions(+), 12 deletions(-)
diff --git a/airflow-core/docs/troubleshooting.rst b/airflow-core/docs/troubleshooting.rst
index 6756ab87e10..b5c904cac96 100644
--- a/airflow-core/docs/troubleshooting.rst
+++ b/airflow-core/docs/troubleshooting.rst
@@ -36,16 +36,26 @@ Below are some example scenarios that could cause a task's state to change by a
- A user marked the task as successful or failed in the Airflow UI.
- An external script or process used the :doc:`Airflow REST API <stable-rest-api-ref>` to change the state of a task.
-TaskRunner killed
------------------
+Process terminated by signal
+----------------------------
Sometimes, Airflow or some adjacent system will kill a task instance's ``TaskRunner``, causing the task instance to fail.
-Here are some examples that could cause such an event:
+Below we discuss a few common cases.
-- A Dag run timeout, specified by ``dagrun_timeout`` in the Dag's definition.
-- An Airflow worker running out of memory
- - Usually, Airflow workers that run out of memory receive a SIGKILL, and the scheduler will fail the corresponding task instance for not having a heartbeat. However, in some scenarios, Airflow kills the task before that happens.
+Dag run timeout
+"""""""""""""""
+
+A dag run timeout can be specified by ``dagrun_timeout`` in the dag's definition.
+The task process would likely be killed with SIGTERM (exit code -15).
+
+Out of memory error (OOM)
+"""""""""""""""""""""""""
+
+When a task process consumes too much memory for a worker, the best case scenario is it is killed
+with SIGTERM (exit code -9). Depending on configuration and infrastructure, it is also
+possible that the whole worker will be killed due to OOM and then the tasks would be marked as
+failed after failing to heartbeat.
Lingering task supervisor processes
-----------------------------------
diff --git a/task-sdk/src/airflow/sdk/execution_time/supervisor.py b/task-sdk/src/airflow/sdk/execution_time/supervisor.py
index e3e4a1cbe83..76a52acd98f 100644
--- a/task-sdk/src/airflow/sdk/execution_time/supervisor.py
+++ b/task-sdk/src/airflow/sdk/execution_time/supervisor.py
@@ -803,11 +803,12 @@ class WatchedSubprocess:
self.process_log.critical(SIGSEGV_MESSAGE)
# psutil turns signal exit codes into an enum for us. Handy. (Otherwise it's a plain integer) if exit_code and (name := getattr(exit_code, "name")):
elif name := getattr(self._exit_code, "name", None):
- message = "Process terminated by signal"
+ message = "Process terminated by signal."
level = logging.ERROR
if self._exit_code == -signal.SIGKILL:
- message += ". For more information, see https://airflow.apache.org/docs/apache-airflow/stable/troubleshooting.html#TaskRunner-killed"
+ message += " Likely out of memory error (OOM)."
level = logging.CRITICAL
+ message += " For more information, see https://airflow.apache.org/docs/apache-airflow/stable/troubleshooting.html#process-terminated-by-signal."
self.process_log.log(level, message, signal=int(self._exit_code), signal_name=name)
elif self._exit_code:
# Run of the mill exit code (1, 42, etc).
diff --git a/task-sdk/tests/task_sdk/execution_time/test_supervisor.py b/task-sdk/tests/task_sdk/execution_time/test_supervisor.py
index e6b0a3dccd9..ce60753bf6a 100644
--- a/task-sdk/tests/task_sdk/execution_time/test_supervisor.py
+++ b/task-sdk/tests/task_sdk/execution_time/test_supervisor.py
@@ -930,21 +930,29 @@ class TestWatchedSubprocess:
mock_logger.warning.assert_not_called()
@pytest.mark.parametrize(
- ["signal_to_raise", "log_pattern"],
+ ["signal_to_raise", "log_pattern", "level"],
(
pytest.param(
signal.SIGKILL,
- re.compile(r"Process terminated by signal. For more information, see"),
+ re.compile(r"Process terminated by signal. Likely out of memory error"),
+ "critical",
id="kill",
),
+ pytest.param(
+ signal.SIGTERM,
+ re.compile(r"Process terminated by signal. For more information"),
+ "error",
+ id="term",
+ ),
pytest.param(
signal.SIGSEGV,
re.compile(r".*SIGSEGV \(Segmentation Violation\) signal indicates", re.DOTALL),
+ "critical",
id="segv",
),
),
)
- def test_exit_by_signal(self, signal_to_raise, log_pattern, cap_structlog, client_with_ti_start):
+ def test_exit_by_signal(self, signal_to_raise, log_pattern, level, cap_structlog, client_with_ti_start):
def subprocess_main():
import faulthandler
import os
@@ -976,7 +984,7 @@ class TestWatchedSubprocess:
rc = proc.wait()
assert {
- "log_level": "critical",
+ "log_level": level,
"event": log_pattern,
} in cap_structlog
assert rc == -signal_to_raise
(airflow) 04/22: Mark React Apps as Experimental for Airflow 3.1 (#55478)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 189272713da1ffab8f1ee51c237acdef6f77bff4
Author: Jens Scheffler <95...@users.noreply.github.com>
AuthorDate: Thu Sep 11 22:19:21 2025 +0200
Mark React Apps as Experimental for Airflow 3.1 (#55478)
* Mark React Apps as Experimental for Airflow 3.1
* Apply suggestions from code review
Co-authored-by: Brent Bovenzi <br...@gmail.com>
---------
Co-authored-by: Brent Bovenzi <br...@gmail.com>
(cherry picked from commit 6da5443399209b57ef928604b01826d07f4f1438)
---
airflow-core/docs/administration-and-deployment/plugins.rst | 3 +++
airflow-core/docs/howto/custom-view-plugin.rst | 7 +++++++
2 files changed, 10 insertions(+)
diff --git a/airflow-core/docs/administration-and-deployment/plugins.rst b/airflow-core/docs/administration-and-deployment/plugins.rst
index a93eb48ffdb..0e50686cd53 100644
--- a/airflow-core/docs/administration-and-deployment/plugins.rst
+++ b/airflow-core/docs/administration-and-deployment/plugins.rst
@@ -111,6 +111,8 @@ looks like:
# A list of dictionaries containing external views and some metadata. See the example below.
external_views = []
# A list of dictionaries containing react apps and some metadata. See the example below.
+ # Note: React apps are only supported in Airflow 3.1 and later.
+ # Note: The React app integration is experimental and interfaces might change in future versions. Particularly, dependency and state interactions between the UI and plugins may need to be refactored for more complex plugin apps.
react_apps = []
# A callback to perform actions when Airflow starts and the plugin is loaded.
@@ -218,6 +220,7 @@ definitions in Airflow.
"category": "browse",
}
+ # Note: The React app integration is experimental and interfaces might change in future versions.
react_app_with_metadata = {
# Name of the React app, this will be displayed in the UI.
"name": "Name of the React App",
diff --git a/airflow-core/docs/howto/custom-view-plugin.rst b/airflow-core/docs/howto/custom-view-plugin.rst
index 1250475fcba..3520de1a652 100644
--- a/airflow-core/docs/howto/custom-view-plugin.rst
+++ b/airflow-core/docs/howto/custom-view-plugin.rst
@@ -54,6 +54,13 @@ available in :doc:`plugin </administration-and-deployment/plugins>`.
Developing React Applications with the Bootstrap Tool
=====================================================
+.. warning::
+ React applications are new in Airflow 3.1 and should be considered experimental. The feature may be
+ subject to changes in future versions without warning based on user feedback and errors reported.
+ Dependency and state interactions between the UI and plugins may need to be refactored, which will also change the bootstrapped example project provided.
+
+|experimental|
+
Airflow provides a React plugin bootstrap tool to help developers quickly create, develop, and integrate external React applications into the core UI. This is the most flexible
and recommended way to customize the Airflow UI.
This tool generates a complete React project structure that builds as a library compatible with dynamic imports and shares React instances with the host Airflow application.
(airflow) 18/22: refactor(hitl): rename response_at to responded_at (#55535)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 6a440b56c1f9b384696d4e18e47d2c18f339241d
Author: Wei Lee <we...@gmail.com>
AuthorDate: Sat Sep 13 08:25:28 2025 +0800
refactor(hitl): rename response_at to responded_at (#55535)
(cherry picked from commit 4709c18da6819906b3c1e8a371b8a665641c0a3d)
---
airflow-core/docs/img/airflow_erd.sha256 | 2 +-
airflow-core/docs/img/airflow_erd.svg | 12 +++++-----
.../src/airflow/api_fastapi/common/parameters.py | 2 +-
.../api_fastapi/core_api/datamodels/hitl.py | 4 ++--
.../api_fastapi/core_api/openapi/_private_ui.yaml | 4 ++--
.../core_api/openapi/v2-rest-api-generated.yaml | 10 ++++-----
.../api_fastapi/core_api/routes/public/hitl.py | 4 ++--
.../airflow/api_fastapi/core_api/routes/ui/dags.py | 2 +-
.../api_fastapi/execution_api/datamodels/hitl.py | 4 ++--
.../api_fastapi/execution_api/routes/hitl.py | 4 ++--
.../0076_3_1_0_add_human_in_the_loop_response.py | 2 +-
airflow-core/src/airflow/models/hitl.py | 6 ++---
.../airflow/ui/openapi-gen/requests/schemas.gen.ts | 10 ++++-----
.../airflow/ui/openapi-gen/requests/types.gen.ts | 4 ++--
.../pages/HITLTaskInstances/HITLResponseForm.tsx | 2 +-
.../pages/HITLTaskInstances/HITLTaskInstances.tsx | 4 ++--
.../core_api/routes/public/test_hitl.py | 14 ++++++------
.../api_fastapi/core_api/routes/ui/test_dags.py | 2 +-
.../execution_api/versions/head/test_hitl.py | 6 ++---
.../src/airflowctl/api/datamodels/generated.py | 4 ++--
.../airflow/providers/standard/operators/hitl.py | 1 +
.../airflow/providers/standard/triggers/hitl.py | 14 +++++++++---
.../tests/unit/standard/operators/test_hitl.py | 26 +++++++++++++++++++---
.../tests/unit/standard/triggers/test_hitl.py | 11 +++++----
.../src/airflow/sdk/api/datamodels/_generated.py | 2 +-
task-sdk/tests/task_sdk/api/test_client.py | 8 +++----
.../tests/task_sdk/execution_time/test_hitl.py | 8 +++----
27 files changed, 102 insertions(+), 70 deletions(-)
diff --git a/airflow-core/docs/img/airflow_erd.sha256 b/airflow-core/docs/img/airflow_erd.sha256
index b6b5a4a9270..ba060d4f5ee 100644
--- a/airflow-core/docs/img/airflow_erd.sha256
+++ b/airflow-core/docs/img/airflow_erd.sha256
@@ -1 +1 @@
-e491b0c58188f06ab4696cc09c765413065069f90d78e27eb53fdac5e2e92c82
\ No newline at end of file
+35e9e07930e138664fb6ff23bc299567a88946734630d84f3d7d95deacf2f4b8
\ 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 f08f88ff3f6..6fbf9c224a4 100644
--- a/airflow-core/docs/img/airflow_erd.svg
+++ b/airflow-core/docs/img/airflow_erd.svg
@@ -1480,13 +1480,13 @@
<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_by</text>
-<text text-anchor="start" x="2300" y="-2063.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2305" y="-2063.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
+<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">response_at</text>
-<text text-anchor="start" x="2286" y="-2038.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2291" y="-2038.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [TIMESTAMP]</text>
+<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>
diff --git a/airflow-core/src/airflow/api_fastapi/common/parameters.py b/airflow-core/src/airflow/api_fastapi/common/parameters.py
index d2dce9743f7..c6ff8aa6f46 100644
--- a/airflow-core/src/airflow/api_fastapi/common/parameters.py
+++ b/airflow-core/src/airflow/api_fastapi/common/parameters.py
@@ -719,7 +719,7 @@ class _PendingActionsFilter(BaseParam[bool]):
sql_select(func.count(HITLDetail.ti_id))
.join(TaskInstance, HITLDetail.ti_id == TaskInstance.id)
.where(
- HITLDetail.response_at.is_(None),
+ HITLDetail.responded_at.is_(None),
TaskInstance.state == TaskInstanceState.DEFERRED,
)
.where(TaskInstance.dag_id == DagModel.dag_id)
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 24a18821f13..f24688c13e5 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
@@ -38,7 +38,7 @@ class HITLDetailResponse(BaseModel):
"""Response of updating a Human-in-the-loop detail."""
responded_by: HITLUser
- response_at: datetime
+ responded_at: datetime
chosen_options: list[str] = Field(min_length=1)
params_input: Mapping = Field(default_factory=dict)
@@ -66,7 +66,7 @@ class HITLDetail(BaseModel):
# Response Content Detail
responded_by_user: HITLUser | None = None
- response_at: datetime | None = None
+ responded_at: datetime | None = None
chosen_options: list[str] | None = None
params_input: dict[str, Any] = Field(default_factory=dict)
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 383b9fca62f..269f60cd4c3 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
@@ -2003,12 +2003,12 @@ components:
anyOf:
- $ref: '#/components/schemas/HITLUser'
- type: 'null'
- response_at:
+ responded_at:
anyOf:
- type: string
format: date-time
- type: 'null'
- title: Response At
+ title: Responded At
chosen_options:
anyOf:
- items:
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 9150f12c012..7c8bba4e9aa 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
@@ -10865,12 +10865,12 @@ components:
anyOf:
- $ref: '#/components/schemas/HITLUser'
- type: 'null'
- response_at:
+ responded_at:
anyOf:
- type: string
format: date-time
- type: 'null'
- title: Response At
+ title: Responded At
chosen_options:
anyOf:
- items:
@@ -10913,10 +10913,10 @@ components:
properties:
responded_by:
$ref: '#/components/schemas/HITLUser'
- response_at:
+ responded_at:
type: string
format: date-time
- title: Response At
+ title: Responded At
chosen_options:
items:
type: string
@@ -10930,7 +10930,7 @@ components:
type: object
required:
- responded_by
- - response_at
+ - responded_at
- chosen_options
title: HITLDetailResponse
description: Response of updating a Human-in-the-loop detail.
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 5f39e18dd13..c444a759ef3 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
@@ -157,7 +157,7 @@ def update_hitl_detail(
)
hitl_detail_model.responded_by = hitl_user
- hitl_detail_model.response_at = timezone.utcnow()
+ hitl_detail_model.responded_at = timezone.utcnow()
hitl_detail_model.chosen_options = update_hitl_detail_payload.chosen_options
hitl_detail_model.params_input = update_hitl_detail_payload.params_input
session.add(hitl_detail_model)
@@ -204,7 +204,7 @@ def get_hitl_details(
allowed_attrs=[
"ti_id",
"subject",
- "response_at",
+ "responded_at",
],
model=HITLDetailModel,
to_replace={
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dags.py b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dags.py
index 9cd5698800a..af4ed341f7f 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dags.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/dags.py
@@ -203,7 +203,7 @@ def get_dags(
)
.join(TaskInstance, HITLDetail.ti_id == TaskInstance.id)
.where(
- HITLDetail.response_at.is_(None),
+ HITLDetail.responded_at.is_(None),
TaskInstance.state == TaskInstanceState.DEFERRED,
)
.where(TaskInstance.dag_id.in_([dag.dag_id for dag in dags]))
diff --git a/airflow-core/src/airflow/api_fastapi/execution_api/datamodels/hitl.py b/airflow-core/src/airflow/api_fastapi/execution_api/datamodels/hitl.py
index a5f16eb8818..4cccbb69e71 100644
--- a/airflow-core/src/airflow/api_fastapi/execution_api/datamodels/hitl.py
+++ b/airflow-core/src/airflow/api_fastapi/execution_api/datamodels/hitl.py
@@ -59,7 +59,7 @@ class HITLDetailResponse(BaseModel):
response_received: bool
responded_by_user: HITLUser | None = None
- response_at: datetime | None
+ responded_at: datetime | None
# It's empty if the user has not yet responded.
chosen_options: list[str] | None
params_input: dict[str, Any] = Field(default_factory=dict)
@@ -77,7 +77,7 @@ class HITLDetailResponse(BaseModel):
return HITLDetailResponse(
response_received=hitl_detail.response_received,
- response_at=hitl_detail.response_at,
+ responded_at=hitl_detail.responded_at,
responded_by_user=hitl_user,
chosen_options=hitl_detail.chosen_options,
params_input=hitl_detail.params_input or {},
diff --git a/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py b/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py
index 85d8e008990..50b9377d2f8 100644
--- a/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py
+++ b/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py
@@ -78,7 +78,7 @@ def upsert_hitl_detail(
# Cleanup the response part of HITLDetail as we only store one response for one task instance.
# It normally happens after retry, we keep only the latest response.
hitl_detail_model.responded_by = None
- hitl_detail_model.response_at = None
+ hitl_detail_model.responded_at = None
hitl_detail_model.chosen_options = None
hitl_detail_model.params_input = {}
session.add(hitl_detail_model)
@@ -117,7 +117,7 @@ def update_hitl_detail(
)
hitl_detail_model.responded_by = None
- hitl_detail_model.response_at = datetime.now(timezone.utc)
+ hitl_detail_model.responded_at = datetime.now(timezone.utc)
hitl_detail_model.chosen_options = payload.chosen_options
hitl_detail_model.params_input = payload.params_input
session.add(hitl_detail_model)
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 60f870ff44c..cd875711ca4 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
@@ -60,7 +60,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("response_at", UtcDateTime, nullable=True),
+ 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),
Column("params_input", sqlalchemy_jsonfield.JSONField(json=json), nullable=False, default={}),
diff --git a/airflow-core/src/airflow/models/hitl.py b/airflow-core/src/airflow/models/hitl.py
index 7f0069688c1..f13c4a6117f 100644
--- a/airflow-core/src/airflow/models/hitl.py
+++ b/airflow-core/src/airflow/models/hitl.py
@@ -99,7 +99,7 @@ class HITLDetail(Base):
assignees = Column(sqlalchemy_jsonfield.JSONField(json=json), nullable=True)
# Response Content Detail
- response_at = Column(UtcDateTime, nullable=True)
+ responded_at = Column(UtcDateTime, nullable=True)
responded_by = Column(sqlalchemy_jsonfield.JSONField(json=json), nullable=True)
chosen_options = Column(
sqlalchemy_jsonfield.JSONField(json=json),
@@ -125,11 +125,11 @@ class HITLDetail(Base):
@hybrid_property
def response_received(self) -> bool:
- return self.response_at is not None
+ return self.responded_at is not None
@response_received.expression # type: ignore[no-redef]
def response_received(cls):
- return cls.response_at.is_not(None)
+ return cls.responded_at.is_not(None)
@hybrid_property
def responded_by_user_id(self) -> str | None:
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 332d112cb7c..1cf2a46b461 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
@@ -3588,7 +3588,7 @@ export const $HITLDetail = {
}
]
},
- response_at: {
+ responded_at: {
anyOf: [
{
type: 'string',
@@ -3598,7 +3598,7 @@ export const $HITLDetail = {
type: 'null'
}
],
- title: 'Response At'
+ title: 'Responded At'
},
chosen_options: {
anyOf: [
@@ -3656,10 +3656,10 @@ export const $HITLDetailResponse = {
responded_by: {
'$ref': '#/components/schemas/HITLUser'
},
- response_at: {
+ responded_at: {
type: 'string',
format: 'date-time',
- title: 'Response At'
+ title: 'Responded At'
},
chosen_options: {
items: {
@@ -3676,7 +3676,7 @@ export const $HITLDetailResponse = {
}
},
type: 'object',
- required: ['responded_by', 'response_at', 'chosen_options'],
+ required: ['responded_by', 'responded_at', 'chosen_options'],
title: 'HITLDetailResponse',
description: 'Response of updating a 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 e88e743d5b5..21a81305bd8 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
@@ -942,7 +942,7 @@ export type HITLDetail = {
};
assigned_users?: Array<HITLUser>;
responded_by_user?: HITLUser | null;
- response_at?: string | null;
+ responded_at?: string | null;
chosen_options?: Array<(string)> | null;
params_input?: {
[key: string]: unknown;
@@ -963,7 +963,7 @@ export type HITLDetailCollection = {
*/
export type HITLDetailResponse = {
responded_by: HITLUser;
- response_at: string;
+ responded_at: string;
chosen_options: Array<(string)>;
params_input?: {
[key: string]: unknown;
diff --git a/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLResponseForm.tsx b/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLResponseForm.tsx
index e703683ce9b..23d10c44713 100644
--- a/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLResponseForm.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLResponseForm.tsx
@@ -98,7 +98,7 @@ export const HITLResponseForm = ({ hitlDetail }: HITLResponseFormProps) => {
{hitlDetail.response_received ? (
<Text color="fg.muted" fontSize="sm">
{translate("response.received")}
- <Time datetime={hitlDetail.response_at} format={DEFAULT_DATETIME_FORMAT} />
+ <Time datetime={hitlDetail.responded_at} format={DEFAULT_DATETIME_FORMAT} />
</Text>
) : undefined}
<Accordion.Root
diff --git a/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLTaskInstances.tsx b/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLTaskInstances.tsx
index 663b4615f29..29e762ee636 100644
--- a/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLTaskInstances.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/HITLTaskInstances/HITLTaskInstances.tsx
@@ -107,8 +107,8 @@ const taskInstanceColumns = ({
header: translate("common:mapIndex"),
},
{
- accessorKey: "response_at",
- cell: ({ row: { original } }) => <Time datetime={original.response_at} />,
+ accessorKey: "responded_at",
+ cell: ({ row: { original } }) => <Time datetime={original.responded_at} />,
header: translate("response.received"),
},
];
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 e20f9fabba4..f29fe60cfef 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
@@ -171,7 +171,7 @@ def sample_hitl_details(sample_tis: list[TaskInstance], session: Session) -> lis
defaults=["1"],
multiple=False,
params={"input": 1},
- response_at=utcnow(),
+ responded_at=utcnow(),
chosen_options=[str(i)],
params_input={"input": i},
responded_by={"id": "test", "name": "test"},
@@ -210,7 +210,7 @@ def expected_sample_hitl_detail_dict(sample_ti: TaskInstance) -> dict[str, Any]:
"params": {"input_1": 1},
"assigned_users": [],
"params_input": {},
- "response_at": None,
+ "responded_at": None,
"chosen_options": None,
"response_received": False,
"subject": "This is subject",
@@ -321,7 +321,7 @@ class TestUpdateHITLDetailEndpoint:
"params_input": {"input_1": 2},
"chosen_options": ["Approve"],
"responded_by": {"id": "test", "name": "test"},
- "response_at": "2025-07-03T00:00:00Z",
+ "responded_at": "2025-07-03T00:00:00Z",
}
audit_log = session.scalar(select(Log))
@@ -350,7 +350,7 @@ class TestUpdateHITLDetailEndpoint:
"params_input": {"input_1": 2},
"chosen_options": ["Approve"],
"responded_by": {"id": "test", "name": "test"},
- "response_at": "2025-07-03T00:00:00Z",
+ "responded_at": "2025-07-03T00:00:00Z",
}
audit_log = session.scalar(select(Log))
@@ -451,7 +451,7 @@ class TestUpdateHITLDetailEndpoint:
"params_input": {"input_1": 2},
"chosen_options": ["Approve"],
"responded_by": {"id": "test", "name": "test"},
- "response_at": "2025-07-03T00:00:00Z",
+ "responded_at": "2025-07-03T00:00:00Z",
}
assert response.status_code == 200
assert response.json() == expected_response
@@ -620,7 +620,7 @@ class TestGetHITLDetailsEndpoint:
("task_instance_operator", lambda x: x["task_instance"]["operator_name"]),
# htil key
("subject", itemgetter("subject")),
- ("response_at", itemgetter("response_at")),
+ ("responded_at", itemgetter("responded_at")),
],
ids=[
"ti_id",
@@ -630,7 +630,7 @@ class TestGetHITLDetailsEndpoint:
"rendered_map_index",
"task_instance_operator",
"subject",
- "response_at",
+ "responded_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 0272b584ab6..d58f29839b6 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
@@ -159,7 +159,7 @@ class TestGetDagRuns(TestPublicDagEndpoint):
options=["Approve", "Reject"],
subject=f"This is subject {i}",
defaults=["Approve"],
- response_at=utcnow(),
+ responded_at=utcnow(),
chosen_options=["Approve"],
responded_by={"id": "test", "name": "test"},
)
diff --git a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
index 33a1b3e79bc..b587b35ee28 100644
--- a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
+++ b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
@@ -48,7 +48,7 @@ default_hitl_detail_request_kwargs: dict[str, Any] = {
"assignees": None,
}
expected_empty_hitl_detail_response_part: dict[str, Any] = {
- "response_at": None,
+ "responded_at": None,
"chosen_options": None,
"responded_by_user": None,
"params_input": {},
@@ -91,7 +91,7 @@ def expected_sample_hitl_detail_dict(sample_ti: TaskInstance) -> dict[str, Any]:
**default_hitl_detail_request_kwargs,
**{
"params_input": {"input_1": 2},
- "response_at": convert_to_utc(datetime(2025, 7, 3, 0, 0, 0)),
+ "responded_at": convert_to_utc(datetime(2025, 7, 3, 0, 0, 0)),
"chosen_options": ["Reject"],
"responded_by": None,
},
@@ -171,7 +171,7 @@ def test_update_hitl_detail(client: Client, sample_ti: TaskInstance) -> None:
assert response.status_code == 200
assert response.json() == {
"params_input": {"input_1": 2},
- "response_at": "2025-07-03T00:00:00Z",
+ "responded_at": "2025-07-03T00:00:00Z",
"chosen_options": ["Reject"],
"response_received": True,
"responded_by_user": None,
diff --git a/airflow-ctl/src/airflowctl/api/datamodels/generated.py b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
index ccad0f23ed8..31da4e13044 100644
--- a/airflow-ctl/src/airflowctl/api/datamodels/generated.py
+++ b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
@@ -1437,7 +1437,7 @@ class HITLDetailResponse(BaseModel):
"""
responded_by: HITLUser
- response_at: Annotated[datetime, Field(title="Response At")]
+ responded_at: Annotated[datetime, Field(title="Responded At")]
chosen_options: Annotated[list[str], Field(min_length=1, title="Chosen Options")]
params_input: Annotated[dict[str, Any] | None, Field(title="Params Input")] = None
@@ -1838,7 +1838,7 @@ class HITLDetail(BaseModel):
params: Annotated[dict[str, Any] | None, Field(title="Params")] = None
assigned_users: Annotated[list[HITLUser] | None, Field(title="Assigned Users")] = None
responded_by_user: HITLUser | None = None
- response_at: Annotated[datetime | None, Field(title="Response At")] = None
+ responded_at: Annotated[datetime | None, Field(title="Responded At")] = None
chosen_options: Annotated[list[str] | None, Field(title="Chosen Options")] = None
params_input: Annotated[dict[str, Any] | None, Field(title="Params Input")] = None
response_received: Annotated[bool | None, Field(title="Response Received")] = False
diff --git a/providers/standard/src/airflow/providers/standard/operators/hitl.py b/providers/standard/src/airflow/providers/standard/operators/hitl.py
index f8e1c631ea6..1c5559adf00 100644
--- a/providers/standard/src/airflow/providers/standard/operators/hitl.py
+++ b/providers/standard/src/airflow/providers/standard/operators/hitl.py
@@ -179,6 +179,7 @@ class HITLOperator(BaseOperator):
return HITLTriggerEventSuccessPayload(
chosen_options=chosen_options,
params_input=params_input,
+ responded_at=event["responded_at"],
responded_by_user=event["responded_by_user"],
)
diff --git a/providers/standard/src/airflow/providers/standard/triggers/hitl.py b/providers/standard/src/airflow/providers/standard/triggers/hitl.py
index bdaa84b01f6..b0b8ec71ef9 100644
--- a/providers/standard/src/airflow/providers/standard/triggers/hitl.py
+++ b/providers/standard/src/airflow/providers/standard/triggers/hitl.py
@@ -45,6 +45,7 @@ class HITLTriggerEventSuccessPayload(TypedDict, total=False):
chosen_options: list[str]
params_input: dict[str, Any]
responded_by_user: HITLUser | None
+ responded_at: datetime
timedout: bool
@@ -106,18 +107,20 @@ class HITLTrigger(BaseTrigger):
if resp.response_received and resp.chosen_options:
if TYPE_CHECKING:
assert resp.responded_by_user is not None
+ assert resp.responded_at is not None
self.log.info(
"[HITL] responded_by=%s (id=%s) options=%s at %s (timeout fallback skipped)",
resp.responded_by_user.name,
resp.responded_by_user.id,
resp.chosen_options,
- resp.response_at,
+ resp.responded_at,
)
yield TriggerEvent(
HITLTriggerEventSuccessPayload(
chosen_options=resp.chosen_options,
params_input=resp.params_input or {},
+ responded_at=resp.responded_at,
responded_by_user=HITLUser(
id=resp.responded_by_user.id,
name=resp.responded_by_user.name,
@@ -136,11 +139,13 @@ class HITLTrigger(BaseTrigger):
)
return
- await sync_to_async(update_hitl_detail_response)(
+ resp = await sync_to_async(update_hitl_detail_response)(
ti_id=self.ti_id,
chosen_options=self.defaults,
params_input=self.params,
)
+ if TYPE_CHECKING:
+ assert resp.responded_at is not None
self.log.info(
"[HITL] timeout reached before receiving response, fallback to default %s", self.defaults
)
@@ -149,6 +154,7 @@ class HITLTrigger(BaseTrigger):
chosen_options=self.defaults,
params_input=self.params,
responded_by_user=None,
+ responded_at=resp.responded_at,
timedout=True,
)
)
@@ -158,17 +164,19 @@ class HITLTrigger(BaseTrigger):
if resp.response_received and resp.chosen_options:
if TYPE_CHECKING:
assert resp.responded_by_user is not None
+ assert resp.responded_at is not None
self.log.info(
"[HITL] responded_by=%s (id=%s) options=%s at %s",
resp.responded_by_user.name,
resp.responded_by_user.id,
resp.chosen_options,
- resp.response_at,
+ resp.responded_at,
)
yield TriggerEvent(
HITLTriggerEventSuccessPayload(
chosen_options=resp.chosen_options,
params_input=resp.params_input or {},
+ responded_at=resp.responded_at,
responded_by_user=HITLUser(
id=resp.responded_by_user.id,
name=resp.responded_by_user.name,
diff --git a/providers/standard/tests/unit/standard/operators/test_hitl.py b/providers/standard/tests/unit/standard/operators/test_hitl.py
index 98d0bf66864..7735a8c04c9 100644
--- a/providers/standard/tests/unit/standard/operators/test_hitl.py
+++ b/providers/standard/tests/unit/standard/operators/test_hitl.py
@@ -183,7 +183,7 @@ class TestHITLOperator:
assert hitl_detail_model.multiple is False
assert hitl_detail_model.params == {"input_1": 1}
assert hitl_detail_model.assignees == [{"id": "test", "name": "test"}]
- assert hitl_detail_model.response_at is None
+ assert hitl_detail_model.responded_at is None
assert hitl_detail_model.responded_by is None
assert hitl_detail_model.chosen_options is None
assert hitl_detail_model.params_input == {}
@@ -230,17 +230,24 @@ class TestHITLOperator:
params={"input": 1},
)
+ responded_at_dt = timezone.utcnow()
+
ret = hitl_op.execute_complete(
context={},
event={
"chosen_options": ["1"],
"params_input": {"input": 2},
+ "responded_at": responded_at_dt,
"responded_by_user": {"id": "test", "name": "test"},
},
)
- assert ret["chosen_options"] == ["1"]
- assert ret["params_input"] == {"input": 2}
+ assert ret == {
+ "chosen_options": ["1"],
+ "params_input": {"input": 2},
+ "responded_at": responded_at_dt,
+ "responded_by_user": {"id": "test", "name": "test"},
+ }
def test_validate_chosen_options_with_invalid_content(self) -> None:
hitl_op = HITLOperator(
@@ -401,11 +408,14 @@ class TestApprovalOperator:
subject="This is subject",
)
+ responded_at_dt = timezone.utcnow()
+
ret = hitl_op.execute_complete(
context={},
event={
"chosen_options": ["Approve"],
"params_input": {},
+ "responded_at": responded_at_dt,
"responded_by_user": {"id": "test", "name": "test"},
},
)
@@ -413,6 +423,7 @@ class TestApprovalOperator:
assert ret == {
"chosen_options": ["Approve"],
"params_input": {},
+ "responded_at": responded_at_dt,
"responded_by_user": {"id": "test", "name": "test"},
}
@@ -433,6 +444,7 @@ class TestApprovalOperator:
event={
"chosen_options": ["Reject"],
"params_input": {},
+ "responded_at": timezone.utcnow(),
"responded_by_user": {"id": "test", "name": "test"},
},
)
@@ -495,6 +507,7 @@ class TestHITLBranchOperator:
event={
"chosen_options": ["branch_1"],
"params_input": {},
+ "responded_at": timezone.utcnow(),
"responded_by_user": {"id": "test", "name": "test"},
},
)
@@ -511,6 +524,8 @@ class TestHITLBranchOperator:
branch_op >> [EmptyOperator(task_id=f"branch_{i}") for i in range(1, 6)]
+ responded_at_dt = timezone.utcnow()
+
dr = dag_maker.create_dagrun()
ti = dr.get_task_instance("make_choice")
with pytest.raises(DownstreamTasksSkipped) as exc_info:
@@ -519,6 +534,7 @@ class TestHITLBranchOperator:
event={
"chosen_options": [f"branch_{i}" for i in range(1, 4)],
"params_input": {},
+ "responded_at": responded_at_dt,
"responded_by_user": {"id": "test", "name": "test"},
},
)
@@ -544,6 +560,7 @@ class TestHITLBranchOperator:
event={
"chosen_options": ["Approve"],
"params_input": {},
+ "responded_at": timezone.utcnow(),
"responded_by_user": {"id": "test", "name": "test"},
},
)
@@ -575,6 +592,7 @@ class TestHITLBranchOperator:
event={
"chosen_options": ["Approve", "KeepAsIs"],
"params_input": {},
+ "responded_at": timezone.utcnow(),
"responded_by_user": {"id": "test", "name": "test"},
},
)
@@ -600,6 +618,7 @@ class TestHITLBranchOperator:
event={
"chosen_options": ["branch_2"],
"params_input": {},
+ "responded_at": timezone.utcnow(),
"responded_by_user": {"id": "test", "name": "test"},
},
)
@@ -625,6 +644,7 @@ class TestHITLBranchOperator:
event={
"chosen_options": ["Approve"],
"params_input": {},
+ "responded_at": timezone.utcnow(),
"responded_by_user": {"id": "test", "name": "test"},
},
)
diff --git a/providers/standard/tests/unit/standard/triggers/test_hitl.py b/providers/standard/tests/unit/standard/triggers/test_hitl.py
index 118bcb77dc1..6aba3b1c60d 100644
--- a/providers/standard/tests/unit/standard/triggers/test_hitl.py
+++ b/providers/standard/tests/unit/standard/triggers/test_hitl.py
@@ -81,7 +81,7 @@ class TestHITLTrigger:
response_received=False,
responded_user_id=None,
responded_user_name=None,
- response_at=None,
+ responded_at=None,
chosen_options=None,
params_input={},
)
@@ -111,7 +111,7 @@ class TestHITLTrigger:
mock_supervisor_comms.send.return_value = HITLDetailResponse(
response_received=False,
responded_by_user=None,
- response_at=None,
+ responded_at=None,
chosen_options=None,
params_input={},
)
@@ -126,6 +126,7 @@ class TestHITLTrigger:
chosen_options=["1"],
params_input={"input": 1},
responded_by_user=None,
+ responded_at=mock.ANY,
timedout=True,
)
)
@@ -154,7 +155,7 @@ class TestHITLTrigger:
mock_supervisor_comms.send.return_value = HITLDetailResponse(
response_received=True,
responded_by_user=HITLUser(id="1", name="test"),
- response_at=action_datetime,
+ responded_at=action_datetime,
chosen_options=["2"],
params_input={},
)
@@ -168,6 +169,7 @@ class TestHITLTrigger:
HITLTriggerEventSuccessPayload(
chosen_options=["2"],
params_input={},
+ responded_at=mock.ANY,
responded_by_user={"id": "1", "name": "test"},
timedout=False,
)
@@ -197,7 +199,7 @@ class TestHITLTrigger:
mock_supervisor_comms.send.return_value = HITLDetailResponse(
response_received=True,
responded_by_user=HITLUser(id="test", name="test"),
- response_at=utcnow(),
+ responded_at=utcnow(),
chosen_options=["3"],
params_input={"input": 50},
)
@@ -210,6 +212,7 @@ class TestHITLTrigger:
HITLTriggerEventSuccessPayload(
chosen_options=["3"],
params_input={"input": 50},
+ responded_at=mock.ANY,
responded_by_user={"id": "test", "name": "test"},
timedout=False,
)
diff --git a/task-sdk/src/airflow/sdk/api/datamodels/_generated.py b/task-sdk/src/airflow/sdk/api/datamodels/_generated.py
index d2151d59d02..8e4c4207b27 100644
--- a/task-sdk/src/airflow/sdk/api/datamodels/_generated.py
+++ b/task-sdk/src/airflow/sdk/api/datamodels/_generated.py
@@ -564,7 +564,7 @@ class HITLDetailResponse(BaseModel):
response_received: Annotated[bool, Field(title="Response Received")]
responded_by_user: HITLUser | None = None
- response_at: Annotated[AwareDatetime | None, Field(title="Response At")] = None
+ responded_at: Annotated[AwareDatetime | None, Field(title="Responded At")] = None
chosen_options: Annotated[list[str] | None, Field(title="Chosen Options")] = None
params_input: Annotated[dict[str, Any] | None, Field(title="Params Input")] = None
diff --git a/task-sdk/tests/task_sdk/api/test_client.py b/task-sdk/tests/task_sdk/api/test_client.py
index 9464296934d..cbd7c80bb01 100644
--- a/task-sdk/tests/task_sdk/api/test_client.py
+++ b/task-sdk/tests/task_sdk/api/test_client.py
@@ -1305,7 +1305,7 @@ class TestHITLOperations:
"params_input": {},
"responded_by_user": {"id": "admin", "name": "admin"},
"response_received": True,
- "response_at": "2025-07-03T00:00:00Z",
+ "responded_at": "2025-07-03T00:00:00Z",
},
)
return httpx.Response(status_code=400, json={"detail": "Bad Request"})
@@ -1321,7 +1321,7 @@ class TestHITLOperations:
assert result.chosen_options == ["Approval"]
assert result.params_input == {}
assert result.responded_by_user == HITLUser(id="admin", name="admin")
- assert result.response_at == timezone.datetime(2025, 7, 3, 0, 0, 0)
+ assert result.responded_at == timezone.datetime(2025, 7, 3, 0, 0, 0)
def test_get_detail_response(self, time_machine: TimeMachineFixture) -> None:
time_machine.move_to(datetime(2025, 7, 3, 0, 0, 0))
@@ -1336,7 +1336,7 @@ class TestHITLOperations:
"params_input": {},
"responded_by_user": {"id": "admin", "name": "admin"},
"response_received": True,
- "response_at": "2025-07-03T00:00:00Z",
+ "responded_at": "2025-07-03T00:00:00Z",
},
)
return httpx.Response(status_code=400, json={"detail": "Bad Request"})
@@ -1348,4 +1348,4 @@ class TestHITLOperations:
assert result.chosen_options == ["Approval"]
assert result.params_input == {}
assert result.responded_by_user == HITLUser(id="admin", name="admin")
- assert result.response_at == timezone.datetime(2025, 7, 3, 0, 0, 0)
+ assert result.responded_at == timezone.datetime(2025, 7, 3, 0, 0, 0)
diff --git a/task-sdk/tests/task_sdk/execution_time/test_hitl.py b/task-sdk/tests/task_sdk/execution_time/test_hitl.py
index ad74ade5cd2..cd3682d922e 100644
--- a/task-sdk/tests/task_sdk/execution_time/test_hitl.py
+++ b/task-sdk/tests/task_sdk/execution_time/test_hitl.py
@@ -62,7 +62,7 @@ def test_update_hitl_detail_response(mock_supervisor_comms) -> None:
mock_supervisor_comms.send.return_value = HITLDetailResponse(
response_received=True,
chosen_options=["Approve"],
- response_at=timestamp,
+ responded_at=timestamp,
responded_by_user=APIHITLUser(id="admin", name="admin"),
params_input={"input_1": 1},
)
@@ -74,7 +74,7 @@ def test_update_hitl_detail_response(mock_supervisor_comms) -> None:
assert resp == HITLDetailResponse(
response_received=True,
chosen_options=["Approve"],
- response_at=timestamp,
+ responded_at=timestamp,
responded_by_user=APIHITLUser(id="admin", name="admin"),
params_input={"input_1": 1},
)
@@ -84,7 +84,7 @@ def test_get_hitl_detail_content_detail(mock_supervisor_comms) -> None:
mock_supervisor_comms.send.return_value = HITLDetailResponse(
response_received=False,
chosen_options=None,
- response_at=None,
+ responded_at=None,
responded_by_user=None,
params_input={},
)
@@ -92,7 +92,7 @@ def test_get_hitl_detail_content_detail(mock_supervisor_comms) -> None:
assert resp == HITLDetailResponse(
response_received=False,
chosen_options=None,
- response_at=None,
+ responded_at=None,
responded_by_user=None,
params_input={},
)
(airflow) 22/22: Update 3.1.0 release notes based on latest cherry-picks
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 900b079d2f9db864619417eda14b45275ec00519
Author: Kaxil Naik <ka...@apache.org>
AuthorDate: Mon Sep 15 01:10:57 2025 +0100
Update 3.1.0 release notes based on latest cherry-picks
---
RELEASE_NOTES.rst | 20 ++++++++++++++++++--
reproducible_build.yaml | 4 ++--
2 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst
index a3113023d3f..c5ec4258277 100644
--- a/RELEASE_NOTES.rst
+++ b/RELEASE_NOTES.rst
@@ -219,6 +219,7 @@ New Features
- Add HITL UI integration with role-based access and form handling (#53035)
- Add HITL API endpoints with filtering and query support (#53376, #53923)
- Add HITL utility functions for generating URLs to required actions page (#54827)
+- Improve HITL user experience with bug fixes, UI enhancements, and data model consistency (#55463, #55539, #55575, #55546, #55543, #55536, #55535)
- Add ordering and filtering support for HITL details endpoints (#55217)
- Add "No Response Received" required action state (#55149)
- Add operator filter for HITL task instances (#54773)
@@ -230,7 +231,7 @@ New Features
- Add language selection interface and browser preference detection (#51369)
- Add translation completeness validation and automated checks (#51166, #51131)
- Add calendar data API endpoints for DAG execution visualization (#52748)
-- Add endpoint to watch DAG runs until completion (#51920)
+- Add endpoint to watch DAG runs until completion (#51920, #53346)
- Add DAG run ID pattern search functionality (#52437)
- Add multi-sorting capabilities for improved data navigation (#53408)
- Add bulk connection deletion API and UI (#51201)
@@ -283,7 +284,9 @@ New Features
- Add deadline relationship to DAG runs and deadline model (#50925, #50093)
- Add DAG run deletion UI (#50368)
- Add task instance deletion UI and endpoint (#50235, #50165)
-- Switch all airflow logging to structlog (#52651, #55434, #55431)
+- Switch all airflow logging to structlog (#52651, #55434, #55431, #55638)
+- Add Filter Bar to Audit Log (#55487)
+- Add Filters UI for Asset View (#54640)
Bug Fixes
^^^^^^^^^
@@ -330,6 +333,12 @@ Bug Fixes
- Output on stdout/stderr from within tasks is now filterable in the Sources list in the UI log view (#55508)
- Redact JWT tokens in task logs (#55499)
- Fix grid view to handle long task name (#55332)
+- Allow slash characters in Variable keys similar to Airflow 2.x (#55324)
+- Fix Grid cache invalidation for multi-run task operations (#55504)
+- Fix Gantt chart rendering issues (#55554)
+- Fix ``XCom`` access in DAG processor callbacks for notifiers (#55542)
+- Fix alignment of arrows in RTL mode for right-to-left languages (#55619)
+- Fix connection form extras not inferring correct type in UI (#55492)
Miscellaneous
^^^^^^^^^^^^^
@@ -354,6 +363,11 @@ Miscellaneous
- Optimize Gantt group expansion with de-bouncing and deferred rendering (#55334)
- Differentiate between triggers and watchers currently running for better visibility (#55376)
- Update color palette and leverage Chakra semantic tokens (#53981)
+- Removed unused config: ``dag_stale_not_seen_duration`` (#55601)
+- Update UI's query client strategy for improved performance (#55528)
+- Unify datetime format across the UI for consistency (#55572)
+- Mark React Apps as Experimental for Airflow 3.1 release (#55478)
+- Improve OOM error messaging for clearer task failure diagnosis (#55602)
Doc Only Changes
^^^^^^^^^^^^^^^^
@@ -363,6 +377,8 @@ Doc Only Changes
- Make term Dag consistent in docs task-sdk (#55100)
- Add DAG bundles triggerer limitation documentation (#55232)
- Add deadline alerts usage guides and best practices (#53727)
+- Remove ``--preview `` flag from ``ruff check`` instructions for Airflow 3 upgrade path (#55516)
+- Add documentation for context parameter (#55377)
Airflow 3.0.6 (2025-08-29)
--------------------------
diff --git a/reproducible_build.yaml b/reproducible_build.yaml
index f083b217d88..79e6df603cc 100644
--- a/reproducible_build.yaml
+++ b/reproducible_build.yaml
@@ -1,2 +1,2 @@
-release-notes-hash: 2b92a6f58dfaa5d414c2ce83d9bf5867
-source-date-epoch: 1757608627
+release-notes-hash: 0638f4d3625aa66fe5660ac969f71c72
+source-date-epoch: 1757895053
(airflow) 11/22: Add autorefresh to Required Actions button (#55543)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit bb09da4770d9318912bc2b12f34baf6d56242bde
Author: Brent Bovenzi <br...@astronomer.io>
AuthorDate: Thu Sep 11 16:22:45 2025 -0600
Add autorefresh to Required Actions button (#55543)
* Add autorefresh to Required Actions button
* Add deferred filter for pending reviews
(cherry picked from commit 20e2b6c7a53e7bad9371e6b74b6c2fe5cda2a80c)
---
.../airflow/ui/src/components/NeedsReviewButton.tsx | 21 +++++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/components/NeedsReviewButton.tsx b/airflow-core/src/airflow/ui/src/components/NeedsReviewButton.tsx
index 78b246f3e36..3ceadb7b55a 100644
--- a/airflow-core/src/airflow/ui/src/components/NeedsReviewButton.tsx
+++ b/airflow-core/src/airflow/ui/src/components/NeedsReviewButton.tsx
@@ -21,6 +21,7 @@ import { useTranslation } from "react-i18next";
import { LuUserRoundPen } from "react-icons/lu";
import { useHumanInTheLoopServiceGetHitlDetails } from "openapi/queries";
+import { useAutoRefresh } from "src/utils/query";
import { StatsCard } from "./StatsCard";
@@ -33,12 +34,20 @@ export const NeedsReviewButton = ({
readonly runId?: string;
readonly taskId?: string;
}) => {
- const { data: hitlStatsData, isLoading } = useHumanInTheLoopServiceGetHitlDetails({
- dagId,
- dagRunId: runId,
- responseReceived: false,
- taskId,
- });
+ const refetchInterval = useAutoRefresh({ dagId });
+ const { data: hitlStatsData, isLoading } = useHumanInTheLoopServiceGetHitlDetails(
+ {
+ dagId,
+ dagRunId: runId,
+ responseReceived: false,
+ state: ["deferred"],
+ taskId,
+ },
+ undefined,
+ {
+ refetchInterval,
+ },
+ );
const hitlTIsCount = hitlStatsData?.hitl_details.length ?? 0;
const { i18n, t: translate } = useTranslation("hitl");
(airflow) 07/22: fix(hitl): make the user model in HITLDetail consistent with airflow user model (#55463)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 7fef4a28399936426760594167fc38bd538533fc
Author: Wei Lee <we...@gmail.com>
AuthorDate: Fri Sep 12 03:10:52 2025 +0800
fix(hitl): make the user model in HITLDetail consistent with airflow user model (#55463)
(cherry picked from commit 30c193b103cf9f48038a5898e72d29b1ad8b3dcc)
---
airflow-core/docs/img/airflow_erd.sha256 | 2 +-
airflow-core/docs/img/airflow_erd.svg | 120 ++++++++++-----------
airflow-core/docs/tutorial/hitl.rst | 3 +
.../src/airflow/api_fastapi/common/parameters.py | 9 +-
.../api_fastapi/core_api/datamodels/hitl.py | 15 ++-
.../api_fastapi/core_api/openapi/_private_ui.yaml | 36 ++++---
.../core_api/openapi/v2-rest-api-generated.yaml | 55 +++++-----
.../api_fastapi/core_api/routes/public/hitl.py | 16 +--
.../api_fastapi/execution_api/datamodels/hitl.py | 24 ++++-
.../api_fastapi/execution_api/routes/hitl.py | 5 +-
.../0076_3_1_0_add_human_in_the_loop_response.py | 5 +-
airflow-core/src/airflow/models/hitl.py | 101 ++++++++++++++++-
.../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 | 65 +++++------
.../ui/openapi-gen/requests/services.gen.ts | 8 +-
.../airflow/ui/openapi-gen/requests/types.gen.ts | 20 ++--
.../core_api/routes/public/test_hitl.py | 30 +++---
.../api_fastapi/core_api/routes/ui/test_dags.py | 3 +-
.../execution_api/versions/head/test_hitl.py | 22 ++--
.../src/airflowctl/api/datamodels/generated.py | 27 +++--
.../standard/example_dags/example_hitl_operator.py | 1 +
.../airflow/providers/standard/operators/hitl.py | 8 +-
.../airflow/providers/standard/triggers/hitl.py | 31 ++++--
.../tests/unit/standard/operators/test_hitl.py | 57 +++++++---
.../tests/unit/standard/triggers/test_hitl.py | 26 +++--
task-sdk/src/airflow/sdk/api/client.py | 5 +-
.../src/airflow/sdk/api/datamodels/_generated.py | 54 ++++++----
task-sdk/src/airflow/sdk/execution_time/hitl.py | 16 ++-
.../src/airflow/sdk/execution_time/supervisor.py | 2 +-
task-sdk/tests/task_sdk/api/test_client.py | 15 ++-
.../tests/task_sdk/execution_time/test_hitl.py | 19 ++--
.../task_sdk/execution_time/test_supervisor.py | 4 +-
36 files changed, 529 insertions(+), 331 deletions(-)
diff --git a/airflow-core/docs/img/airflow_erd.sha256 b/airflow-core/docs/img/airflow_erd.sha256
index ee840ec21f2..b6b5a4a9270 100644
--- a/airflow-core/docs/img/airflow_erd.sha256
+++ b/airflow-core/docs/img/airflow_erd.sha256
@@ -1 +1 @@
-c05cefbe080ed889ebe132a0285756db477ff28256bbc1e86da1e053873f6478
\ No newline at end of file
+e491b0c58188f06ab4696cc09c765413065069f90d78e27eb53fdac5e2e92c82
\ 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 a129862ad11..f08f88ff3f6 100644
--- a/airflow-core/docs/img/airflow_erd.svg
+++ b/airflow-core/docs/img/airflow_erd.svg
@@ -1437,72 +1437,68 @@
<!-- hitl_detail -->
<g id="node43" class="node">
<title>hitl_detail</title>
-<polygon fill="none" stroke="black" points="2170,-2328 2170,-2356 2463,-2356 2463,-2328 2170,-2328"/>
-<text text-anchor="start" x="2272.5" y="-2339.2" font-family="Helvetica,sans-Serif" font-weight="bold" font-size="16.00">hitl_detail</text>
-<polygon fill="none" stroke="black" points="2170,-2303 2170,-2328 2463,-2328 2463,-2303 2170,-2303"/>
-<text text-anchor="start" x="2175" y="-2312.8" font-family="Helvetica,sans-Serif" text-decoration="underline" font-size="14.00">ti_id</text>
-<text text-anchor="start" x="2204" y="-2312.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2209" y="-2312.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [UUID]</text>
-<text text-anchor="start" x="2261" y="-2312.8" font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
-<polygon fill="none" stroke="black" points="2170,-2278 2170,-2303 2463,-2303 2463,-2278 2170,-2278"/>
-<text text-anchor="start" x="2175" y="-2287.8" font-family="Helvetica,sans-Serif" font-size="14.00">body</text>
-<text text-anchor="start" x="2210" y="-2287.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2215" y="-2287.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [TEXT]</text>
-<polygon fill="none" stroke="black" points="2170,-2253 2170,-2278 2463,-2278 2463,-2253 2170,-2253"/>
-<text text-anchor="start" x="2175" y="-2262.8" font-family="Helvetica,sans-Serif" font-size="14.00">chosen_options</text>
-<text text-anchor="start" x="2283" y="-2262.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2288" y="-2262.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
-<polygon fill="none" stroke="black" points="2170,-2228 2170,-2253 2463,-2253 2463,-2228 2170,-2228"/>
-<text text-anchor="start" x="2175" y="-2237.8" font-family="Helvetica,sans-Serif" font-size="14.00">defaults</text>
-<text text-anchor="start" x="2232" y="-2237.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2237" y="-2237.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
-<polygon fill="none" stroke="black" points="2170,-2203 2170,-2228 2463,-2228 2463,-2203 2170,-2203"/>
-<text text-anchor="start" x="2175" y="-2212.8" font-family="Helvetica,sans-Serif" font-size="14.00">multiple</text>
-<text text-anchor="start" x="2232" y="-2212.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2237" y="-2212.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [BOOLEAN]</text>
-<polygon fill="none" stroke="black" points="2170,-2178 2170,-2203 2463,-2203 2463,-2178 2170,-2178"/>
-<text text-anchor="start" x="2175" y="-2187.8" font-family="Helvetica,sans-Serif" font-size="14.00">options</text>
-<text text-anchor="start" x="2227" y="-2187.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2232" y="-2187.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
-<text text-anchor="start" x="2283" y="-2187.8" font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
-<polygon fill="none" stroke="black" points="2170,-2153 2170,-2178 2463,-2178 2463,-2153 2170,-2153"/>
-<text text-anchor="start" x="2175" y="-2162.8" font-family="Helvetica,sans-Serif" font-size="14.00">params</text>
-<text text-anchor="start" x="2228" y="-2162.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2233" y="-2162.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
-<text text-anchor="start" x="2284" y="-2162.8" font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
-<polygon fill="none" stroke="black" points="2170,-2128 2170,-2153 2463,-2153 2463,-2128 2170,-2128"/>
-<text text-anchor="start" x="2175" y="-2137.8" font-family="Helvetica,sans-Serif" font-size="14.00">params_input</text>
-<text text-anchor="start" x="2271" y="-2137.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2276" y="-2137.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
-<text text-anchor="start" x="2327" y="-2137.8" font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
-<polygon fill="none" stroke="black" points="2170,-2103 2170,-2128 2463,-2128 2463,-2103 2170,-2103"/>
-<text text-anchor="start" x="2175" y="-2112.8" font-family="Helvetica,sans-Serif" font-size="14.00">responded_user_id</text>
-<text text-anchor="start" x="2306" y="-2112.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2311" y="-2112.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [VARCHAR(128)]</text>
-<polygon fill="none" stroke="black" points="2170,-2078 2170,-2103 2463,-2103 2463,-2078 2170,-2078"/>
-<text text-anchor="start" x="2175" y="-2087.8" font-family="Helvetica,sans-Serif" font-size="14.00">responded_user_name</text>
-<text text-anchor="start" x="2332" y="-2087.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2337" y="-2087.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [VARCHAR(128)]</text>
-<polygon fill="none" stroke="black" points="2170,-2053 2170,-2078 2463,-2078 2463,-2053 2170,-2053"/>
-<text text-anchor="start" x="2175" y="-2062.8" font-family="Helvetica,sans-Serif" font-size="14.00">respondents</text>
-<text text-anchor="start" x="2262" y="-2062.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2267" y="-2062.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</text>
-<polygon fill="none" stroke="black" points="2170,-2028 2170,-2053 2463,-2053 2463,-2028 2170,-2028"/>
-<text text-anchor="start" x="2175" y="-2037.8" font-family="Helvetica,sans-Serif" font-size="14.00">response_at</text>
-<text text-anchor="start" x="2259" y="-2037.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2264" y="-2037.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [TIMESTAMP]</text>
-<polygon fill="none" stroke="black" points="2170,-2003 2170,-2028 2463,-2028 2463,-2003 2170,-2003"/>
-<text text-anchor="start" x="2175" y="-2012.8" font-family="Helvetica,sans-Serif" font-size="14.00">subject</text>
-<text text-anchor="start" x="2226" y="-2012.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
-<text text-anchor="start" x="2231" y="-2012.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [TEXT]</text>
-<text text-anchor="start" x="2281" y="-2012.8" font-family="Helvetica,sans-Serif" font-size="14.00"> NOT NULL</text>
+<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_by</text>
+<text text-anchor="start" x="2300" y="-2063.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2305" y="-2063.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [JSON]</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">response_at</text>
+<text text-anchor="start" x="2286" y="-2038.8" font-family="Helvetica,sans-Serif" font-size="14.00"> </text>
+<text text-anchor="start" x="2291" y="-2038.8" font-family="Helvetica,sans-Serif" font-size="14.00"> [TIMESTAMP]</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>
</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.12,-1859.57C2103.71,-1904.92 2128.13,-1949.64 2154,-1991 2156.4,-1994.84 2158.87,-1998.68 2161.41,-2002.51"/>
-<text text-anchor="start" x="2151.41" y="-1991.31" 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>
+<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>
</g>
<!-- task_map -->
<g id="node44" class="node">
diff --git a/airflow-core/docs/tutorial/hitl.rst b/airflow-core/docs/tutorial/hitl.rst
index b104bc7d033..a84ef735598 100644
--- a/airflow-core/docs/tutorial/hitl.rst
+++ b/airflow-core/docs/tutorial/hitl.rst
@@ -95,6 +95,9 @@ Approval or Rejection
---------------------
A specialized form of option selection, which has only 'Approval' and 'Rejection' as options.
+You can also set the ``assigned_users`` to restrict the users allowed to respond for a HITL operator.
+It should be a list of user ids and user names (both needed) (e.g., ``[{"id": "1", "name": "user1"}, {"id": "2", "name": "user2"}]``.
+ONLY the users within this list will be allowed to respond.
.. exampleinclude:: /../../providers/standard/src/airflow/providers/standard/example_dags/example_hitl_operator.py
:language: python
diff --git a/airflow-core/src/airflow/api_fastapi/common/parameters.py b/airflow-core/src/airflow/api_fastapi/common/parameters.py
index 7a555454cd1..d2dce9743f7 100644
--- a/airflow-core/src/airflow/api_fastapi/common/parameters.py
+++ b/airflow-core/src/airflow/api_fastapi/common/parameters.py
@@ -1025,15 +1025,16 @@ QueryHITLDetailResponseReceivedFilter = Annotated[
)
),
]
+
QueryHITLDetailRespondedUserIdFilter = Annotated[
FilterParam[list[str]],
Depends(
filter_param_factory(
- HITLDetail.responded_user_id,
+ HITLDetail.responded_by_user_id,
list[str],
FilterOptionEnum.ANY_EQUAL,
default_factory=list,
- filter_name="responded_user_id",
+ filter_name="responded_by_user_id",
)
),
]
@@ -1041,11 +1042,11 @@ QueryHITLDetailRespondedUserNameFilter = Annotated[
FilterParam[list[str]],
Depends(
filter_param_factory(
- HITLDetail.responded_user_name,
+ HITLDetail.responded_by_user_name,
list[str],
FilterOptionEnum.ANY_EQUAL,
default_factory=list,
- filter_name="responded_user_name",
+ filter_name="responded_by_user_name",
)
),
]
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 ea0f045539e..24a18821f13 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
@@ -37,13 +37,19 @@ class UpdateHITLDetailPayload(BaseModel):
class HITLDetailResponse(BaseModel):
"""Response of updating a Human-in-the-loop detail."""
- responded_user_id: str
- responded_user_name: str
+ responded_by: HITLUser
response_at: datetime
chosen_options: list[str] = Field(min_length=1)
params_input: Mapping = Field(default_factory=dict)
+class HITLUser(BaseModel):
+ """Schema for a Human-in-the-loop users."""
+
+ id: str
+ name: str
+
+
class HITLDetail(BaseModel):
"""Schema for Human-in-the-loop detail."""
@@ -56,11 +62,10 @@ class HITLDetail(BaseModel):
defaults: list[str] | None = None
multiple: bool = False
params: dict[str, Any] = Field(default_factory=dict)
- respondents: list[str] | None = None
+ assigned_users: list[HITLUser] = Field(default_factory=list)
# Response Content Detail
- responded_user_id: str | None = None
- responded_user_name: str | None = None
+ responded_by_user: HITLUser | None = None
response_at: datetime | None = None
chosen_options: list[str] | None = None
params_input: dict[str, Any] = Field(default_factory=dict)
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 a4d85b16dd3..383b9fca62f 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
@@ -1994,23 +1994,15 @@ components:
additionalProperties: true
type: object
title: Params
- respondents:
- anyOf:
- - items:
- type: string
- type: array
- - type: 'null'
- title: Respondents
- responded_user_id:
- anyOf:
- - type: string
- - type: 'null'
- title: Responded User Id
- responded_user_name:
+ assigned_users:
+ items:
+ $ref: '#/components/schemas/HITLUser'
+ type: array
+ title: Assigned Users
+ responded_by_user:
anyOf:
- - type: string
+ - $ref: '#/components/schemas/HITLUser'
- type: 'null'
- title: Responded User Name
response_at:
anyOf:
- type: string
@@ -2039,6 +2031,20 @@ components:
- subject
title: HITLDetail
description: Schema for Human-in-the-loop detail.
+ HITLUser:
+ properties:
+ id:
+ type: string
+ title: Id
+ name:
+ type: string
+ title: Name
+ type: object
+ required:
+ - id
+ - name
+ title: HITLUser
+ description: Schema for a Human-in-the-loop users.
HTTPExceptionResponse:
properties:
detail:
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 f9057f2d5ca..9150f12c012 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
@@ -8200,22 +8200,22 @@ paths:
- type: boolean
- type: 'null'
title: Response Received
- - name: responded_user_id
+ - name: responded_by_user_id
in: query
required: false
schema:
type: array
items:
type: string
- title: Responded User Id
- - name: responded_user_name
+ title: Responded By User Id
+ - name: responded_by_user_name
in: query
required: false
schema:
type: array
items:
type: string
- title: Responded User Name
+ title: Responded By User Name
- name: subject_search
in: query
required: false
@@ -10856,23 +10856,15 @@ components:
additionalProperties: true
type: object
title: Params
- respondents:
- anyOf:
- - items:
- type: string
- type: array
- - type: 'null'
- title: Respondents
- responded_user_id:
- anyOf:
- - type: string
- - type: 'null'
- title: Responded User Id
- responded_user_name:
+ assigned_users:
+ items:
+ $ref: '#/components/schemas/HITLUser'
+ type: array
+ title: Assigned Users
+ responded_by_user:
anyOf:
- - type: string
+ - $ref: '#/components/schemas/HITLUser'
- type: 'null'
- title: Responded User Name
response_at:
anyOf:
- type: string
@@ -10919,12 +10911,8 @@ components:
description: Schema for a collection of Human-in-the-loop details.
HITLDetailResponse:
properties:
- responded_user_id:
- type: string
- title: Responded User Id
- responded_user_name:
- type: string
- title: Responded User Name
+ responded_by:
+ $ref: '#/components/schemas/HITLUser'
response_at:
type: string
format: date-time
@@ -10941,12 +10929,25 @@ components:
title: Params Input
type: object
required:
- - responded_user_id
- - responded_user_name
+ - responded_by
- response_at
- chosen_options
title: HITLDetailResponse
description: Response of updating a Human-in-the-loop detail.
+ HITLUser:
+ properties:
+ id:
+ type: string
+ title: Id
+ name:
+ type: string
+ title: Name
+ type: object
+ required:
+ - id
+ - name
+ title: HITLUser
+ description: Schema for a Human-in-the-loop users.
HTTPExceptionResponse:
properties:
detail:
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 e04714e0a91..5f39e18dd13 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
@@ -53,7 +53,7 @@ from airflow.api_fastapi.core_api.openapi.exceptions import create_openapi_http_
from airflow.api_fastapi.core_api.security import GetUserDep, ReadableTIFilterDep, requires_access_dag
from airflow.api_fastapi.logging.decorators import action_logging
from airflow.models.dagrun import DagRun
-from airflow.models.hitl import HITLDetail as HITLDetailModel
+from airflow.models.hitl import HITLDetail as HITLDetailModel, HITLUser
from airflow.models.taskinstance import TaskInstance as TI
hitl_router = AirflowRouter(tags=["HumanInTheLoop"], prefix="/hitlDetails")
@@ -144,19 +144,19 @@ def update_hitl_detail(
user_id = user.get_id()
user_name = user.get_name()
- if hitl_detail_model.respondents:
- if isinstance(user_id, int):
- # FabAuthManager (ab_user) store user id as integer, but common interface is string type
- user_id = str(user_id)
- if user_id not in hitl_detail_model.respondents:
+ if isinstance(user_id, int):
+ # FabAuthManager (ab_user) store user id as integer, but common interface is string type
+ user_id = str(user_id)
+ hitl_user = HITLUser(id=user_id, name=user_name)
+ if hitl_detail_model.assigned_users:
+ if hitl_user not in hitl_detail_model.assigned_users:
log.error("User=%s (id=%s) is not a respondent for the task", user_name, user_id)
raise HTTPException(
status.HTTP_403_FORBIDDEN,
f"User={user_name} (id={user_id}) is not a respondent for the task.",
)
- hitl_detail_model.responded_user_id = user_id
- hitl_detail_model.responded_user_name = user_name
+ hitl_detail_model.responded_by = hitl_user
hitl_detail_model.response_at = timezone.utcnow()
hitl_detail_model.chosen_options = update_hitl_detail_payload.chosen_options
hitl_detail_model.params_input = update_hitl_detail_payload.params_input
diff --git a/airflow-core/src/airflow/api_fastapi/execution_api/datamodels/hitl.py b/airflow-core/src/airflow/api_fastapi/execution_api/datamodels/hitl.py
index 4162d2cf771..a5f16eb8818 100644
--- a/airflow-core/src/airflow/api_fastapi/execution_api/datamodels/hitl.py
+++ b/airflow-core/src/airflow/api_fastapi/execution_api/datamodels/hitl.py
@@ -26,6 +26,13 @@ from airflow.api_fastapi.core_api.base import BaseModel
from airflow.models.hitl import HITLDetail
+class HITLUser(BaseModel):
+ """Schema for a Human-in-the-loop users."""
+
+ id: str
+ name: str
+
+
class HITLDetailRequest(BaseModel):
"""Schema for the request part of a Human-in-the-loop detail for a specific task instance."""
@@ -36,7 +43,7 @@ class HITLDetailRequest(BaseModel):
defaults: list[str] | None = None
multiple: bool = False
params: dict[str, Any] = Field(default_factory=dict)
- respondents: list[str] | None = None
+ assigned_users: list[HITLUser] = Field(default_factory=list)
class UpdateHITLDetailPayload(BaseModel):
@@ -51,8 +58,7 @@ class HITLDetailResponse(BaseModel):
"""Schema for the response part of a Human-in-the-loop detail for a specific task instance."""
response_received: bool
- responded_user_name: str | None
- responded_user_id: str | None
+ responded_by_user: HITLUser | None = None
response_at: datetime | None
# It's empty if the user has not yet responded.
chosen_options: list[str] | None
@@ -60,11 +66,19 @@ class HITLDetailResponse(BaseModel):
@classmethod
def from_hitl_detail_orm(cls, hitl_detail: HITLDetail) -> HITLDetailResponse:
+ hitl_user = (
+ HITLUser(
+ id=hitl_detail.responded_by_user_id,
+ name=hitl_detail.responded_by_user_name,
+ )
+ if hitl_detail.responded_by_user
+ else None
+ )
+
return HITLDetailResponse(
response_received=hitl_detail.response_received,
response_at=hitl_detail.response_at,
- responded_user_id=hitl_detail.responded_user_id,
- responded_user_name=hitl_detail.responded_user_name,
+ responded_by_user=hitl_user,
chosen_options=hitl_detail.chosen_options,
params_input=hitl_detail.params_input or {},
)
diff --git a/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py b/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py
index c9efbda78f0..8a6606bd280 100644
--- a/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py
+++ b/airflow-core/src/airflow/api_fastapi/execution_api/routes/hitl.py
@@ -71,7 +71,7 @@ def upsert_hitl_detail(
defaults=payload.defaults,
multiple=payload.multiple,
params=payload.params,
- respondents=payload.respondents,
+ assignees=[user.model_dump() for user in payload.assigned_users],
)
session.add(hitl_detail_model)
elif hitl_detail_model.response_received:
@@ -116,8 +116,7 @@ def update_hitl_detail(
f"Human-in-the-loop detail for Task Instance with id {ti_id_str} already exists.",
)
- hitl_detail_model.responded_user_id = HITLDetail.DEFAULT_USER_NAME
- hitl_detail_model.responded_user_name = HITLDetail.DEFAULT_USER_NAME
+ hitl_detail_model.responded_by = HITLDetail.DEFAULT_USER
hitl_detail_model.response_at = datetime.now(timezone.utc)
hitl_detail_model.chosen_options = payload.chosen_options
hitl_detail_model.params_input = payload.params_input
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 3e0ede0183e..60f870ff44c 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
@@ -59,10 +59,9 @@ def upgrade():
Column("defaults", sqlalchemy_jsonfield.JSONField(json=json), nullable=True),
Column("multiple", Boolean, unique=False, default=False),
Column("params", sqlalchemy_jsonfield.JSONField(json=json), nullable=False, default={}),
- Column("respondents", sqlalchemy_jsonfield.JSONField(json=json), nullable=True),
+ Column("assignees", sqlalchemy_jsonfield.JSONField(json=json), nullable=True),
Column("response_at", UtcDateTime, nullable=True),
- Column("responded_user_id", String(128), nullable=True),
- Column("responded_user_name", String(128), nullable=True),
+ Column("responded_by", sqlalchemy_jsonfield.JSONField(json=json), nullable=True),
Column("chosen_options", sqlalchemy_jsonfield.JSONField(json=json), nullable=True),
Column("params_input", sqlalchemy_jsonfield.JSONField(json=json), nullable=False, default={}),
ForeignKeyConstraint(
diff --git a/airflow-core/src/airflow/models/hitl.py b/airflow-core/src/airflow/models/hitl.py
index 448939e2c6f..e2055344bc3 100644
--- a/airflow-core/src/airflow/models/hitl.py
+++ b/airflow-core/src/airflow/models/hitl.py
@@ -16,16 +16,68 @@
# under the License.
from __future__ import annotations
+from typing import TYPE_CHECKING, Any, TypedDict
+
import sqlalchemy_jsonfield
-from sqlalchemy import Boolean, Column, ForeignKeyConstraint, String, Text
+from sqlalchemy import Boolean, Column, ForeignKeyConstraint, String, Text, func, literal
from sqlalchemy.dialects import postgresql
+from sqlalchemy.ext.compiler import compiles
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship
+from sqlalchemy.sql.functions import FunctionElement
from airflow.models.base import Base
from airflow.settings import json
from airflow.utils.sqlalchemy import UtcDateTime
+if TYPE_CHECKING:
+ from sqlalchemy.sql import ColumnElement
+ from sqlalchemy.sql.compiler import SQLCompiler
+
+
+class JSONExtract(FunctionElement):
+ """
+ Cross-dialect JSON key extractor.
+
+ :meta: private
+ """
+
+ type = String()
+ inherit_cache = True
+
+ def __init__(self, column: ColumnElement[Any], key: str, **kwargs: dict[str, Any]) -> None:
+ super().__init__(column, literal(key), **kwargs)
+
+
+@compiles(JSONExtract, "postgresql")
+def compile_postgres(element: JSONExtract, compiler: SQLCompiler, **kwargs: dict[str, Any]) -> str:
+ """
+ Compile JSONExtract for PostgreSQL.
+
+ :meta: private
+ """
+ column, key = element.clauses
+ return compiler.process(func.json_extract_path_text(column, key), **kwargs)
+
+
+@compiles(JSONExtract, "sqlite")
+@compiles(JSONExtract, "mysql")
+def compile_sqlite_mysql(element: JSONExtract, compiler: SQLCompiler, **kwargs: dict[str, Any]) -> str:
+ """
+ Compile JSONExtract for SQLite/MySQL.
+
+ :meta: private
+ """
+ column, key = element.clauses
+ return compiler.process(func.json_extract(column, f"$.{key.value}"), **kwargs)
+
+
+class HITLUser(TypedDict):
+ """Typed dict for saving a Human-in-the-loop user information."""
+
+ id: str
+ name: str
+
class HITLDetail(Base):
"""Human-in-the-loop request and corresponding response."""
@@ -44,12 +96,11 @@ class HITLDetail(Base):
defaults = Column(sqlalchemy_jsonfield.JSONField(json=json), nullable=True)
multiple = Column(Boolean, unique=False, default=False)
params = Column(sqlalchemy_jsonfield.JSONField(json=json), nullable=False, default={})
- respondents = Column(sqlalchemy_jsonfield.JSONField(json=json), nullable=True)
+ assignees = Column(sqlalchemy_jsonfield.JSONField(json=json), nullable=True)
# Response Content Detail
response_at = Column(UtcDateTime, nullable=True)
- responded_user_id = Column(String(128), nullable=True)
- responded_user_name = Column(String(128), nullable=True)
+ responded_by = Column(sqlalchemy_jsonfield.JSONField(json=json), nullable=True)
chosen_options = Column(
sqlalchemy_jsonfield.JSONField(json=json),
nullable=True,
@@ -80,4 +131,44 @@ class HITLDetail(Base):
def response_received(cls):
return cls.response_at.is_not(None)
- DEFAULT_USER_NAME = "Fallback to defaults"
+ @hybrid_property
+ def responded_by_user_id(self) -> str | None:
+ return self.responded_by["id"] if self.responded_by else None
+
+ @responded_by_user_id.expression # type: ignore[no-redef]
+ def responded_by_user_id(cls):
+ return JSONExtract(cls.responded_by, "id")
+
+ @hybrid_property
+ def responded_by_user_name(self) -> str | None:
+ return self.responded_by["name"] if self.responded_by else None
+
+ @responded_by_user_name.expression # type: ignore[no-redef]
+ def responded_by_user_name(cls):
+ return JSONExtract(cls.responded_by, "name")
+
+ @hybrid_property
+ def assigned_users(self) -> list[HITLUser]:
+ if not self.assignees:
+ return []
+ return [
+ HITLUser(
+ id=assignee["id"],
+ name=assignee["name"],
+ )
+ for assignee in self.assignees
+ ]
+
+ @hybrid_property
+ def responded_by_user(self) -> HITLUser | None:
+ if self.responded_by is None:
+ return None
+ return HITLUser(
+ id=self.responded_by["id"],
+ name=self.responded_by["name"],
+ )
+
+ DEFAULT_USER = HITLUser(
+ id="Fallback to defaults",
+ name="Fallback to defaults",
+ )
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 457aac893ee..d4ffa8098f9 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -717,7 +717,7 @@ export const UseHumanInTheLoopServiceGetHitlDetailKeyFn = ({ dagId, dagRunId, ma
export type HumanInTheLoopServiceGetHitlDetailsDefaultResponse = Awaited<ReturnType<typeof HumanInTheLoopService.getHitlDetails>>;
export type HumanInTheLoopServiceGetHitlDetailsQueryResult<TData = HumanInTheLoopServiceGetHitlDetailsDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>;
export const useHumanInTheLoopServiceGetHitlDetailsKey = "HumanInTheLoopServiceGetHitlDetails";
-export const UseHumanInTheLoopServiceGetHitlDetailsKeyFn = ({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedUserId, respondedUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: {
+export const UseHumanInTheLoopServiceGetHitlDetailsKeyFn = ({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: {
bodySearch?: string;
dagId?: string;
dagIdPattern?: string;
@@ -725,14 +725,14 @@ export const UseHumanInTheLoopServiceGetHitlDetailsKeyFn = ({ bodySearch, dagId,
limit?: number;
offset?: number;
orderBy?: string[];
- respondedUserId?: string[];
- respondedUserName?: string[];
+ respondedByUserId?: string[];
+ respondedByUserName?: string[];
responseReceived?: boolean;
state?: string[];
subjectSearch?: string;
taskId?: string;
taskIdPattern?: string;
-} = {}, queryKey?: Array<unknown>) => [useHumanInTheLoopServiceGetHitlDetailsKey, ...(queryKey ?? [{ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedUserId, respondedUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }])];
+} = {}, queryKey?: Array<unknown>) => [useHumanInTheLoopServiceGetHitlDetailsKey, ...(queryKey ?? [{ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }])];
export type MonitorServiceGetHealthDefaultResponse = Awaited<ReturnType<typeof MonitorService.getHealth>>;
export type MonitorServiceGetHealthQueryResult<TData = MonitorServiceGetHealthDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>;
export const useMonitorServiceGetHealthKey = "MonitorServiceGetHealth";
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 fac229938af..da76a35a8ce 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -1371,14 +1371,14 @@ export const ensureUseHumanInTheLoopServiceGetHitlDetailData = (queryClient: Que
* @param data.taskIdPattern SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). Regular expressions are **not** supported.
* @param data.state
* @param data.responseReceived
-* @param data.respondedUserId
-* @param data.respondedUserName
+* @param data.respondedByUserId
+* @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.
* @returns HITLDetailCollection Successful Response
* @throws ApiError
*/
-export const ensureUseHumanInTheLoopServiceGetHitlDetailsData = (queryClient: QueryClient, { bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedUserId, respondedUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: {
+export const ensureUseHumanInTheLoopServiceGetHitlDetailsData = (queryClient: QueryClient, { bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: {
bodySearch?: string;
dagId?: string;
dagIdPattern?: string;
@@ -1386,14 +1386,14 @@ export const ensureUseHumanInTheLoopServiceGetHitlDetailsData = (queryClient: Qu
limit?: number;
offset?: number;
orderBy?: string[];
- respondedUserId?: string[];
- respondedUserName?: string[];
+ respondedByUserId?: string[];
+ respondedByUserName?: string[];
responseReceived?: boolean;
state?: string[];
subjectSearch?: string;
taskId?: string;
taskIdPattern?: string;
-} = {}) => queryClient.ensureQueryData({ queryKey: Common.UseHumanInTheLoopServiceGetHitlDetailsKeyFn({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedUserId, respondedUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }), queryFn: () => HumanInTheLoopService.getHitlDetails({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedUserId, respondedUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }) });
+} = {}) => queryClient.ensureQueryData({ queryKey: Common.UseHumanInTheLoopServiceGetHitlDetailsKeyFn({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }), queryFn: () => HumanInTheLoopService.getHitlDetails({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskId [...]
/**
* Get Health
* @returns HealthInfoResponse Successful Response
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 1870bb85c32..af773c94bdd 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -1371,14 +1371,14 @@ export const prefetchUseHumanInTheLoopServiceGetHitlDetail = (queryClient: Query
* @param data.taskIdPattern SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). Regular expressions are **not** supported.
* @param data.state
* @param data.responseReceived
-* @param data.respondedUserId
-* @param data.respondedUserName
+* @param data.respondedByUserId
+* @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.
* @returns HITLDetailCollection Successful Response
* @throws ApiError
*/
-export const prefetchUseHumanInTheLoopServiceGetHitlDetails = (queryClient: QueryClient, { bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedUserId, respondedUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: {
+export const prefetchUseHumanInTheLoopServiceGetHitlDetails = (queryClient: QueryClient, { bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: {
bodySearch?: string;
dagId?: string;
dagIdPattern?: string;
@@ -1386,14 +1386,14 @@ export const prefetchUseHumanInTheLoopServiceGetHitlDetails = (queryClient: Quer
limit?: number;
offset?: number;
orderBy?: string[];
- respondedUserId?: string[];
- respondedUserName?: string[];
+ respondedByUserId?: string[];
+ respondedByUserName?: string[];
responseReceived?: boolean;
state?: string[];
subjectSearch?: string;
taskId?: string;
taskIdPattern?: string;
-} = {}) => queryClient.prefetchQuery({ queryKey: Common.UseHumanInTheLoopServiceGetHitlDetailsKeyFn({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedUserId, respondedUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }), queryFn: () => HumanInTheLoopService.getHitlDetails({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedUserId, respondedUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }) });
+} = {}) => queryClient.prefetchQuery({ queryKey: Common.UseHumanInTheLoopServiceGetHitlDetailsKeyFn({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }), queryFn: () => HumanInTheLoopService.getHitlDetails({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPa [...]
/**
* Get Health
* @returns HealthInfoResponse Successful Response
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 e48235d6184..6c70c69ac5c 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -1371,14 +1371,14 @@ export const useHumanInTheLoopServiceGetHitlDetail = <TData = Common.HumanInTheL
* @param data.taskIdPattern SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). Regular expressions are **not** supported.
* @param data.state
* @param data.responseReceived
-* @param data.respondedUserId
-* @param data.respondedUserName
+* @param data.respondedByUserId
+* @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.
* @returns HITLDetailCollection Successful Response
* @throws ApiError
*/
-export const useHumanInTheLoopServiceGetHitlDetails = <TData = Common.HumanInTheLoopServiceGetHitlDetailsDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedUserId, respondedUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: {
+export const useHumanInTheLoopServiceGetHitlDetails = <TData = Common.HumanInTheLoopServiceGetHitlDetailsDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: {
bodySearch?: string;
dagId?: string;
dagIdPattern?: string;
@@ -1386,14 +1386,14 @@ export const useHumanInTheLoopServiceGetHitlDetails = <TData = Common.HumanInThe
limit?: number;
offset?: number;
orderBy?: string[];
- respondedUserId?: string[];
- respondedUserName?: string[];
+ respondedByUserId?: string[];
+ respondedByUserName?: string[];
responseReceived?: boolean;
state?: string[];
subjectSearch?: string;
taskId?: string;
taskIdPattern?: string;
-} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseHumanInTheLoopServiceGetHitlDetailsKeyFn({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedUserId, respondedUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }, queryKey), queryFn: () => HumanInTheLoopService.getHitlDetails({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy [...]
+} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: Common.UseHumanInTheLoopServiceGetHitlDetailsKeyFn({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }, queryKey), queryFn: () => HumanInTheLoopService.getHitlDetails({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, ord [...]
/**
* Get Health
* @returns HealthInfoResponse Successful Response
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 a8578766cd5..79a5f974e07 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -1371,14 +1371,14 @@ export const useHumanInTheLoopServiceGetHitlDetailSuspense = <TData = Common.Hum
* @param data.taskIdPattern SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). Regular expressions are **not** supported.
* @param data.state
* @param data.responseReceived
-* @param data.respondedUserId
-* @param data.respondedUserName
+* @param data.respondedByUserId
+* @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.
* @returns HITLDetailCollection Successful Response
* @throws ApiError
*/
-export const useHumanInTheLoopServiceGetHitlDetailsSuspense = <TData = Common.HumanInTheLoopServiceGetHitlDetailsDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedUserId, respondedUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: {
+export const useHumanInTheLoopServiceGetHitlDetailsSuspense = <TData = Common.HumanInTheLoopServiceGetHitlDetailsDefaultResponse, TError = unknown, TQueryKey extends Array<unknown> = unknown[]>({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }: {
bodySearch?: string;
dagId?: string;
dagIdPattern?: string;
@@ -1386,14 +1386,14 @@ export const useHumanInTheLoopServiceGetHitlDetailsSuspense = <TData = Common.Hu
limit?: number;
offset?: number;
orderBy?: string[];
- respondedUserId?: string[];
- respondedUserName?: string[];
+ respondedByUserId?: string[];
+ respondedByUserName?: string[];
responseReceived?: boolean;
state?: string[];
subjectSearch?: string;
taskId?: string;
taskIdPattern?: string;
-} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: Common.UseHumanInTheLoopServiceGetHitlDetailsKeyFn({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedUserId, respondedUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }, queryKey), queryFn: () => HumanInTheLoopService.getHitlDetails({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, [...]
+} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: Common.UseHumanInTheLoopServiceGetHitlDetailsKeyFn({ bodySearch, dagId, dagIdPattern, dagRunId, limit, offset, orderBy, respondedByUserId, respondedByUserName, responseReceived, state, subjectSearch, taskId, taskIdPattern }, queryKey), queryFn: () => HumanInTheLoopService.getHitlDetails({ bodySearch, dagId, dagIdPattern, dagRunId, limit, off [...]
/**
* Get Health
* @returns HealthInfoResponse Successful Response
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 72795ca4551..332d112cb7c 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
@@ -3571,41 +3571,22 @@ export const $HITLDetail = {
type: 'object',
title: 'Params'
},
- respondents: {
- anyOf: [
- {
- items: {
- type: 'string'
- },
- type: 'array'
- },
- {
- type: 'null'
- }
- ],
- title: 'Respondents'
- },
- responded_user_id: {
- anyOf: [
- {
- type: 'string'
- },
- {
- type: 'null'
- }
- ],
- title: 'Responded User Id'
+ assigned_users: {
+ items: {
+ '$ref': '#/components/schemas/HITLUser'
+ },
+ type: 'array',
+ title: 'Assigned Users'
},
- responded_user_name: {
+ responded_by_user: {
anyOf: [
{
- type: 'string'
+ '$ref': '#/components/schemas/HITLUser'
},
{
type: 'null'
}
- ],
- title: 'Responded User Name'
+ ]
},
response_at: {
anyOf: [
@@ -3672,13 +3653,8 @@ export const $HITLDetailCollection = {
export const $HITLDetailResponse = {
properties: {
- responded_user_id: {
- type: 'string',
- title: 'Responded User Id'
- },
- responded_user_name: {
- type: 'string',
- title: 'Responded User Name'
+ responded_by: {
+ '$ref': '#/components/schemas/HITLUser'
},
response_at: {
type: 'string',
@@ -3700,11 +3676,28 @@ export const $HITLDetailResponse = {
}
},
type: 'object',
- required: ['responded_user_id', 'responded_user_name', 'response_at', 'chosen_options'],
+ required: ['responded_by', 'response_at', 'chosen_options'],
title: 'HITLDetailResponse',
description: 'Response of updating a Human-in-the-loop detail.'
} as const;
+export const $HITLUser = {
+ properties: {
+ id: {
+ type: 'string',
+ title: 'Id'
+ },
+ name: {
+ type: 'string',
+ title: 'Name'
+ }
+ },
+ type: 'object',
+ required: ['id', 'name'],
+ title: 'HITLUser',
+ description: 'Schema for a Human-in-the-loop users.'
+} as const;
+
export const $HTTPExceptionResponse = {
properties: {
detail: {
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 5c944d4bc6a..ed570c2fc9d 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
@@ -3623,8 +3623,8 @@ export class HumanInTheLoopService {
* @param data.taskIdPattern SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). Regular expressions are **not** supported.
* @param data.state
* @param data.responseReceived
- * @param data.respondedUserId
- * @param data.respondedUserName
+ * @param data.respondedByUserId
+ * @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.
* @returns HITLDetailCollection Successful Response
@@ -3645,8 +3645,8 @@ export class HumanInTheLoopService {
task_id_pattern: data.taskIdPattern,
state: data.state,
response_received: data.responseReceived,
- responded_user_id: data.respondedUserId,
- responded_user_name: data.respondedUserName,
+ responded_by_user_id: data.respondedByUserId,
+ responded_by_user_name: data.respondedByUserName,
subject_search: data.subjectSearch,
body_search: data.bodySearch
},
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 c1320c1c635..e88e743d5b5 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
@@ -940,9 +940,8 @@ export type HITLDetail = {
params?: {
[key: string]: unknown;
};
- respondents?: Array<(string)> | null;
- responded_user_id?: string | null;
- responded_user_name?: string | null;
+ assigned_users?: Array<HITLUser>;
+ responded_by_user?: HITLUser | null;
response_at?: string | null;
chosen_options?: Array<(string)> | null;
params_input?: {
@@ -963,8 +962,7 @@ export type HITLDetailCollection = {
* Response of updating a Human-in-the-loop detail.
*/
export type HITLDetailResponse = {
- responded_user_id: string;
- responded_user_name: string;
+ responded_by: HITLUser;
response_at: string;
chosen_options: Array<(string)>;
params_input?: {
@@ -972,6 +970,14 @@ export type HITLDetailResponse = {
};
};
+/**
+ * Schema for a Human-in-the-loop users.
+ */
+export type HITLUser = {
+ id: string;
+ name: string;
+};
+
/**
* HTTPException Model used for error response.
*/
@@ -3100,8 +3106,8 @@ export type GetHitlDetailsData = {
limit?: number;
offset?: number;
orderBy?: Array<(string)>;
- respondedUserId?: Array<(string)>;
- respondedUserName?: Array<(string)>;
+ respondedByUserId?: Array<(string)>;
+ respondedByUserName?: Array<(string)>;
responseReceived?: boolean | null;
state?: Array<(string)>;
/**
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 9dfd3559920..e20f9fabba4 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
@@ -31,6 +31,7 @@ from sqlalchemy.orm import Session
from airflow._shared.timezones.timezone import 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
if TYPE_CHECKING:
@@ -77,7 +78,7 @@ def sample_hitl_detail(sample_ti: TaskInstance, session: Session) -> HITLDetail:
defaults=["Approve"],
multiple=False,
params={"input_1": 1},
- respondents=None,
+ assignees=None,
)
session.add(hitl_detail_model)
session.commit()
@@ -95,7 +96,7 @@ def sample_hitl_detail_non_respondent(sample_ti: TaskInstance, session: Session)
defaults=["Approve"],
multiple=False,
params={"input_1": 1},
- respondents=["non_test"],
+ assignees=[HITLUser(id="non_test", name="non_test")],
)
session.add(hitl_detail_model)
session.commit()
@@ -113,7 +114,7 @@ def sample_hitl_detail_respondent(sample_ti: TaskInstance, session: Session) ->
defaults=["Approve"],
multiple=False,
params={"input_1": 1},
- respondents=["test"],
+ assignees=[HITLUser(id="test", name="test")],
)
session.add(hitl_detail_model)
session.commit()
@@ -173,8 +174,7 @@ def sample_hitl_details(sample_tis: list[TaskInstance], session: Session) -> lis
response_at=utcnow(),
chosen_options=[str(i)],
params_input={"input": i},
- responded_user_id="test",
- responded_user_name="test",
+ responded_by={"id": "test", "name": "test"},
)
for i, ti in enumerate(sample_tis[5:])
]
@@ -208,14 +208,13 @@ def expected_sample_hitl_detail_dict(sample_ti: TaskInstance) -> dict[str, Any]:
"multiple": False,
"options": ["Approve", "Reject"],
"params": {"input_1": 1},
- "respondents": None,
+ "assigned_users": [],
"params_input": {},
"response_at": None,
"chosen_options": None,
"response_received": False,
"subject": "This is subject",
- "responded_user_id": None,
- "responded_user_name": None,
+ "responded_by_user": None,
"task_instance": {
"dag_display_name": DAG_ID,
"dag_id": DAG_ID,
@@ -321,8 +320,7 @@ class TestUpdateHITLDetailEndpoint:
assert response.json() == {
"params_input": {"input_1": 2},
"chosen_options": ["Approve"],
- "responded_user_id": "test",
- "responded_user_name": "test",
+ "responded_by": {"id": "test", "name": "test"},
"response_at": "2025-07-03T00:00:00Z",
}
@@ -332,7 +330,7 @@ class TestUpdateHITLDetailEndpoint:
@time_machine.travel(datetime(2025, 7, 3, 0, 0, 0), tick=False)
@pytest.mark.usefixtures("sample_hitl_detail_respondent")
@pytest.mark.parametrize("map_index", [None, -1])
- def test_should_respond_200_to_respondent_user(
+ def test_should_respond_200_to_assigned_users(
self,
test_client: TestClient,
sample_ti_url_identifier: str,
@@ -351,8 +349,7 @@ class TestUpdateHITLDetailEndpoint:
assert response.json() == {
"params_input": {"input_1": 2},
"chosen_options": ["Approve"],
- "responded_user_id": "test",
- "responded_user_name": "test",
+ "responded_by": {"id": "test", "name": "test"},
"response_at": "2025-07-03T00:00:00Z",
}
@@ -453,8 +450,7 @@ class TestUpdateHITLDetailEndpoint:
expected_response = {
"params_input": {"input_1": 2},
"chosen_options": ["Approve"],
- "responded_user_id": "test",
- "responded_user_name": "test",
+ "responded_by": {"id": "test", "name": "test"},
"response_at": "2025-07-03T00:00:00Z",
}
assert response.status_code == 200
@@ -579,8 +575,8 @@ class TestGetHITLDetailsEndpoint:
({"body_search": "this is"}, 8),
({"response_received": False}, 5),
({"response_received": True}, 3),
- ({"responded_user_id": ["test"]}, 3),
- ({"responded_user_name": ["test"]}, 3),
+ ({"responded_by_user_id": ["test"]}, 3),
+ ({"responded_by_user_name": ["test"]}, 3),
],
ids=[
"dag_id_pattern_hitl_dag",
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 ea1f6293151..0272b584ab6 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
@@ -161,7 +161,7 @@ class TestGetDagRuns(TestPublicDagEndpoint):
defaults=["Approve"],
response_at=utcnow(),
chosen_options=["Approve"],
- responded_user_id="test",
+ responded_by={"id": "test", "name": "test"},
)
for i in range(3, 5)
]
@@ -187,6 +187,7 @@ class TestGetDagRuns(TestPublicDagEndpoint):
"params": {},
"params_input": {},
"response_received": False,
+ "assigned_users": [],
}
for i in range(3)
],
diff --git a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
index 085fc7ae6a8..764d5a48ccd 100644
--- a/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
+++ b/airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_hitl.py
@@ -45,13 +45,12 @@ default_hitl_detail_request_kwargs: dict[str, Any] = {
"defaults": ["Approve"],
"multiple": False,
"params": {"input_1": 1},
- "respondents": None,
+ "assignees": None,
}
expected_empty_hitl_detail_response_part: dict[str, Any] = {
"response_at": None,
"chosen_options": None,
- "responded_user_id": None,
- "responded_user_name": None,
+ "responded_by_user": None,
"params_input": {},
"response_received": False,
}
@@ -94,8 +93,7 @@ def expected_sample_hitl_detail_dict(sample_ti: TaskInstance) -> dict[str, Any]:
"params_input": {"input_1": 2},
"response_at": convert_to_utc(datetime(2025, 7, 3, 0, 0, 0)),
"chosen_options": ["Reject"],
- "responded_user_id": "Fallback to defaults",
- "responded_user_name": "Fallback to defaults",
+ "responded_by": {"id": "Fallback to defaults", "name": "Fallback to defaults"},
},
},
],
@@ -125,11 +123,15 @@ def test_upsert_hitl_detail(
**default_hitl_detail_request_kwargs,
},
)
- assert response.status_code == 201
- assert response.json() == {
+
+ expected_json = {
"ti_id": ti.id,
**default_hitl_detail_request_kwargs,
}
+ expected_json["assigned_users"] = expected_json.pop("assignees") or []
+
+ assert response.status_code == 201
+ assert response.json() == expected_json
def test_upsert_hitl_detail_with_empty_option(
@@ -172,8 +174,10 @@ def test_update_hitl_detail(client: Client, sample_ti: TaskInstance) -> None:
"response_at": "2025-07-03T00:00:00Z",
"chosen_options": ["Reject"],
"response_received": True,
- "responded_user_id": "Fallback to defaults",
- "responded_user_name": "Fallback to defaults",
+ "responded_by_user": {
+ "id": "Fallback to defaults",
+ "name": "Fallback to defaults",
+ },
}
diff --git a/airflow-ctl/src/airflowctl/api/datamodels/generated.py b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
index 37ecd597a5c..ccad0f23ed8 100644
--- a/airflow-ctl/src/airflowctl/api/datamodels/generated.py
+++ b/airflow-ctl/src/airflowctl/api/datamodels/generated.py
@@ -525,16 +525,13 @@ class FastAPIRootMiddlewareResponse(BaseModel):
name: Annotated[str, Field(title="Name")]
-class HITLDetailResponse(BaseModel):
+class HITLUser(BaseModel):
"""
- Response of updating a Human-in-the-loop detail.
+ Schema for a Human-in-the-loop users.
"""
- responded_user_id: Annotated[str, Field(title="Responded User Id")]
- responded_user_name: Annotated[str, Field(title="Responded User Name")]
- response_at: Annotated[datetime, Field(title="Response At")]
- chosen_options: Annotated[list[str], Field(min_length=1, title="Chosen Options")]
- params_input: Annotated[dict[str, Any] | None, Field(title="Params Input")] = None
+ id: Annotated[str, Field(title="Id")]
+ name: Annotated[str, Field(title="Name")]
class HTTPExceptionResponse(BaseModel):
@@ -1434,6 +1431,17 @@ class EventLogCollectionResponse(BaseModel):
total_entries: Annotated[int, Field(title="Total Entries")]
+class HITLDetailResponse(BaseModel):
+ """
+ Response of updating a Human-in-the-loop detail.
+ """
+
+ responded_by: HITLUser
+ response_at: Annotated[datetime, Field(title="Response At")]
+ chosen_options: Annotated[list[str], Field(min_length=1, title="Chosen Options")]
+ params_input: Annotated[dict[str, Any] | None, Field(title="Params Input")] = None
+
+
class HTTPValidationError(BaseModel):
detail: Annotated[list[ValidationError] | None, Field(title="Detail")] = None
@@ -1828,9 +1836,8 @@ class HITLDetail(BaseModel):
defaults: Annotated[list[str] | None, Field(title="Defaults")] = None
multiple: Annotated[bool | None, Field(title="Multiple")] = False
params: Annotated[dict[str, Any] | None, Field(title="Params")] = None
- respondents: Annotated[list[str] | None, Field(title="Respondents")] = None
- responded_user_id: Annotated[str | None, Field(title="Responded User Id")] = None
- responded_user_name: Annotated[str | None, Field(title="Responded User Name")] = None
+ assigned_users: Annotated[list[HITLUser] | None, Field(title="Assigned Users")] = None
+ responded_by_user: HITLUser | None = None
response_at: Annotated[datetime | None, Field(title="Response At")] = None
chosen_options: Annotated[list[str] | None, Field(title="Chosen Options")] = None
params_input: Annotated[dict[str, Any] | None, Field(title="Params Input")] = None
diff --git a/providers/standard/src/airflow/providers/standard/example_dags/example_hitl_operator.py b/providers/standard/src/airflow/providers/standard/example_dags/example_hitl_operator.py
index dd7b4dc277f..44d77ba4959 100644
--- a/providers/standard/src/airflow/providers/standard/example_dags/example_hitl_operator.py
+++ b/providers/standard/src/airflow/providers/standard/example_dags/example_hitl_operator.py
@@ -140,6 +140,7 @@ with DAG(
notifiers=[hitl_request_callback],
on_success_callback=hitl_success_callback,
on_failure_callback=hitl_failure_callback,
+ assigned_users=[{"id": "admin", "name": "admin"}],
)
# [END howto_hitl_approval_operator]
diff --git a/providers/standard/src/airflow/providers/standard/operators/hitl.py b/providers/standard/src/airflow/providers/standard/operators/hitl.py
index 440abf9a516..f8e1c631ea6 100644
--- a/providers/standard/src/airflow/providers/standard/operators/hitl.py
+++ b/providers/standard/src/airflow/providers/standard/operators/hitl.py
@@ -41,6 +41,7 @@ from airflow.sdk.timezone import utcnow
if TYPE_CHECKING:
from airflow.sdk.definitions.context import Context
+ from airflow.sdk.execution_time.hitl import HITLUser
from airflow.sdk.types import RuntimeTaskInstanceProtocol
@@ -70,7 +71,7 @@ class HITLOperator(BaseOperator):
multiple: bool = False,
params: ParamsDict | dict[str, Any] | None = None,
notifiers: Sequence[BaseNotifier] | BaseNotifier | None = None,
- respondents: str | list[str] | None = None,
+ assigned_users: HITLUser | list[HITLUser] | None = None,
**kwargs,
) -> None:
super().__init__(**kwargs)
@@ -86,7 +87,7 @@ class HITLOperator(BaseOperator):
self.notifiers: Sequence[BaseNotifier] = (
[notifiers] if isinstance(notifiers, BaseNotifier) else notifiers or []
)
- self.respondents = [respondents] if isinstance(respondents, str) else respondents
+ self.assigned_users = [assigned_users] if isinstance(assigned_users, dict) else assigned_users
self.validate_options()
self.validate_params()
@@ -138,7 +139,7 @@ class HITLOperator(BaseOperator):
defaults=self.defaults,
multiple=self.multiple,
params=self.serialized_params,
- respondents=self.respondents,
+ assigned_users=self.assigned_users,
)
if self.execution_timeout:
@@ -178,6 +179,7 @@ class HITLOperator(BaseOperator):
return HITLTriggerEventSuccessPayload(
chosen_options=chosen_options,
params_input=params_input,
+ responded_by_user=event["responded_by_user"],
)
def process_trigger_event_error(self, event: dict[str, Any]) -> None:
diff --git a/providers/standard/src/airflow/providers/standard/triggers/hitl.py b/providers/standard/src/airflow/providers/standard/triggers/hitl.py
index c5f32800058..a9299ae2407 100644
--- a/providers/standard/src/airflow/providers/standard/triggers/hitl.py
+++ b/providers/standard/src/airflow/providers/standard/triggers/hitl.py
@@ -25,12 +25,13 @@ if not AIRFLOW_V_3_1_PLUS:
import asyncio
from collections.abc import AsyncIterator
from datetime import datetime
-from typing import Any, Literal, TypedDict
+from typing import TYPE_CHECKING, Any, Literal, TypedDict
from uuid import UUID
from asgiref.sync import sync_to_async
from airflow.sdk.execution_time.hitl import (
+ HITLUser,
get_hitl_detail_content_detail,
update_hitl_detail_response,
)
@@ -43,6 +44,7 @@ class HITLTriggerEventSuccessPayload(TypedDict, total=False):
chosen_options: list[str]
params_input: dict[str, Any]
+ responded_by_user: HITLUser
timedout: bool
@@ -100,12 +102,15 @@ class HITLTrigger(BaseTrigger):
if self.timeout_datetime and self.timeout_datetime < utcnow():
# Fetch latest HITL detail before fallback
resp = await sync_to_async(get_hitl_detail_content_detail)(ti_id=self.ti_id)
+ # Response already received, yield success and exit
if resp.response_received and resp.chosen_options:
- # Response already received, yield success and exit
+ if TYPE_CHECKING:
+ assert resp.responded_by_user is not None
+
self.log.info(
"[HITL] responded_by=%s (id=%s) options=%s at %s (timeout fallback skipped)",
- resp.responded_user_name,
- resp.responded_user_id,
+ resp.responded_by_user.name,
+ resp.responded_by_user.id,
resp.chosen_options,
resp.response_at,
)
@@ -113,6 +118,10 @@ class HITLTrigger(BaseTrigger):
HITLTriggerEventSuccessPayload(
chosen_options=resp.chosen_options,
params_input=resp.params_input or {},
+ responded_by_user=HITLUser(
+ id=resp.responded_by_user.id,
+ name=resp.responded_by_user.name,
+ ),
timedout=False,
)
)
@@ -139,6 +148,10 @@ class HITLTrigger(BaseTrigger):
HITLTriggerEventSuccessPayload(
chosen_options=self.defaults,
params_input=self.params,
+ responded_by_user=HITLUser(
+ id="Fallback to defaults",
+ name="Fallback to defaults",
+ ),
timedout=True,
)
)
@@ -146,10 +159,12 @@ class HITLTrigger(BaseTrigger):
resp = await sync_to_async(get_hitl_detail_content_detail)(ti_id=self.ti_id)
if resp.response_received and resp.chosen_options:
+ if TYPE_CHECKING:
+ assert resp.responded_by_user is not None
self.log.info(
"[HITL] responded_by=%s (id=%s) options=%s at %s",
- resp.responded_user_name,
- resp.responded_user_id,
+ resp.responded_by_user.name,
+ resp.responded_by_user.id,
resp.chosen_options,
resp.response_at,
)
@@ -157,6 +172,10 @@ class HITLTrigger(BaseTrigger):
HITLTriggerEventSuccessPayload(
chosen_options=resp.chosen_options,
params_input=resp.params_input or {},
+ responded_by_user=HITLUser(
+ id=resp.responded_by_user.id,
+ name=resp.responded_by_user.name,
+ ),
timedout=False,
)
)
diff --git a/providers/standard/tests/unit/standard/operators/test_hitl.py b/providers/standard/tests/unit/standard/operators/test_hitl.py
index 18646ab118d..98d0bf66864 100644
--- a/providers/standard/tests/unit/standard/operators/test_hitl.py
+++ b/providers/standard/tests/unit/standard/operators/test_hitl.py
@@ -43,6 +43,7 @@ from airflow.providers.standard.operators.hitl import (
)
from airflow.sdk import Param, timezone
from airflow.sdk.definitions.param import ParamsDict
+from airflow.sdk.execution_time.hitl import HITLUser
from tests_common.test_utils.config import conf_vars
@@ -66,7 +67,7 @@ def hitl_task_and_ti_for_generating_link(dag_maker: DagMaker) -> tuple[HITLOpera
options=["1", "2", "3", "4", "5"],
body="This is body",
defaults=["1"],
- respondents="test",
+ assigned_users=HITLUser(id="test", name="test"),
multiple=True,
params=ParamsDict({"input_1": 1, "input_2": 2, "input_3": 3}),
)
@@ -165,7 +166,7 @@ class TestHITLOperator:
options=["1", "2", "3", "4", "5"],
body="This is body",
defaults=["1"],
- respondents="test",
+ assigned_users=HITLUser(id="test", name="test"),
multiple=False,
params=ParamsDict({"input_1": 1}),
notifiers=[notifier],
@@ -181,10 +182,9 @@ class TestHITLOperator:
assert hitl_detail_model.defaults == ["1"]
assert hitl_detail_model.multiple is False
assert hitl_detail_model.params == {"input_1": 1}
- assert hitl_detail_model.respondents == ["test"]
+ assert hitl_detail_model.assignees == [{"id": "test", "name": "test"}]
assert hitl_detail_model.response_at is None
- assert hitl_detail_model.responded_user_id is None
- assert hitl_detail_model.responded_user_name is None
+ assert hitl_detail_model.responded_by is None
assert hitl_detail_model.chosen_options is None
assert hitl_detail_model.params_input == {}
@@ -232,7 +232,11 @@ class TestHITLOperator:
ret = hitl_op.execute_complete(
context={},
- event={"chosen_options": ["1"], "params_input": {"input": 2}},
+ event={
+ "chosen_options": ["1"],
+ "params_input": {"input": 2},
+ "responded_by_user": {"id": "test", "name": "test"},
+ },
)
assert ret["chosen_options"] == ["1"]
@@ -253,6 +257,7 @@ class TestHITLOperator:
event={
"chosen_options": ["not exists"],
"params_input": {"input": 2},
+ "responded_by_user": {"id": "test", "name": "test"},
},
)
@@ -271,6 +276,7 @@ class TestHITLOperator:
event={
"chosen_options": ["1"],
"params_input": {"no such key": 2, "input": 333},
+ "responded_by_user": {"id": "test", "name": "test"},
},
)
@@ -397,12 +403,17 @@ class TestApprovalOperator:
ret = hitl_op.execute_complete(
context={},
- event={"chosen_options": ["Approve"], "params_input": {}},
+ event={
+ "chosen_options": ["Approve"],
+ "params_input": {},
+ "responded_by_user": {"id": "test", "name": "test"},
+ },
)
assert ret == {
"chosen_options": ["Approve"],
"params_input": {},
+ "responded_by_user": {"id": "test", "name": "test"},
}
def test_execute_complete_with_downstream_tasks(self, dag_maker) -> None:
@@ -419,7 +430,11 @@ class TestApprovalOperator:
with pytest.raises(DownstreamTasksSkipped) as exc_info:
hitl_op.execute_complete(
context={"ti": ti, "task": ti.task},
- event={"chosen_options": ["Reject"], "params_input": {}},
+ event={
+ "chosen_options": ["Reject"],
+ "params_input": {},
+ "responded_by_user": {"id": "test", "name": "test"},
+ },
)
assert set(exc_info.value.tasks) == {"op1"}
@@ -480,6 +495,7 @@ class TestHITLBranchOperator:
event={
"chosen_options": ["branch_1"],
"params_input": {},
+ "responded_by_user": {"id": "test", "name": "test"},
},
)
assert set(exc_info.value.tasks) == set((f"branch_{i}", -1) for i in range(2, 6))
@@ -503,6 +519,7 @@ class TestHITLBranchOperator:
event={
"chosen_options": [f"branch_{i}" for i in range(1, 4)],
"params_input": {},
+ "responded_by_user": {"id": "test", "name": "test"},
},
)
assert set(exc_info.value.tasks) == set((f"branch_{i}", -1) for i in range(4, 6))
@@ -524,7 +541,11 @@ class TestHITLBranchOperator:
with pytest.raises(DownstreamTasksSkipped) as exc:
op.execute_complete(
context={"ti": ti, "task": ti.task},
- event={"chosen_options": ["Approve"], "params_input": {}},
+ event={
+ "chosen_options": ["Approve"],
+ "params_input": {},
+ "responded_by_user": {"id": "test", "name": "test"},
+ },
)
# checks to see that the "archive" task was skipped
assert set(exc.value.tasks) == {("archive", -1)}
@@ -551,7 +572,11 @@ class TestHITLBranchOperator:
with pytest.raises(DownstreamTasksSkipped) as exc:
op.execute_complete(
context={"ti": ti, "task": ti.task},
- event={"chosen_options": ["Approve", "KeepAsIs"], "params_input": {}},
+ event={
+ "chosen_options": ["Approve", "KeepAsIs"],
+ "params_input": {},
+ "responded_by_user": {"id": "test", "name": "test"},
+ },
)
# publish + keep chosen → only "other" skipped
assert set(exc.value.tasks) == {("other", -1)}
@@ -572,7 +597,11 @@ class TestHITLBranchOperator:
with pytest.raises(DownstreamTasksSkipped) as exc:
op.execute_complete(
context={"ti": ti, "task": ti.task},
- event={"chosen_options": ["branch_2"], "params_input": {}},
+ event={
+ "chosen_options": ["branch_2"],
+ "params_input": {},
+ "responded_by_user": {"id": "test", "name": "test"},
+ },
)
assert set(exc.value.tasks) == {("branch_1", -1)}
@@ -593,7 +622,11 @@ class TestHITLBranchOperator:
with pytest.raises(AirflowException, match="downstream|not found"):
op.execute_complete(
context={"ti": ti, "task": ti.task},
- event={"chosen_options": ["Approve"], "params_input": {}},
+ event={
+ "chosen_options": ["Approve"],
+ "params_input": {},
+ "responded_by_user": {"id": "test", "name": "test"},
+ },
)
@pytest.mark.parametrize("bad", [123, ["publish"], {"x": "y"}, b"publish"])
diff --git a/providers/standard/tests/unit/standard/triggers/test_hitl.py b/providers/standard/tests/unit/standard/triggers/test_hitl.py
index 7b33715e68d..ef82f9e09d9 100644
--- a/providers/standard/tests/unit/standard/triggers/test_hitl.py
+++ b/providers/standard/tests/unit/standard/triggers/test_hitl.py
@@ -31,7 +31,7 @@ from unittest import mock
from uuid6 import uuid7
from airflow._shared.timezones.timezone import utc, utcnow
-from airflow.api_fastapi.execution_api.datamodels.hitl import HITLDetailResponse
+from airflow.api_fastapi.execution_api.datamodels.hitl import HITLDetailResponse, HITLUser
from airflow.providers.standard.triggers.hitl import (
HITLTrigger,
HITLTriggerEventFailurePayload,
@@ -110,8 +110,7 @@ class TestHITLTrigger:
)
mock_supervisor_comms.send.return_value = HITLDetailResponse(
response_received=False,
- responded_user_id=None,
- responded_user_name=None,
+ responded_by_user=None,
response_at=None,
chosen_options=None,
params_input={},
@@ -123,7 +122,12 @@ class TestHITLTrigger:
event = await trigger_task
assert event == TriggerEvent(
- HITLTriggerEventSuccessPayload(chosen_options=["1"], params_input={"input": 1}, timedout=True)
+ HITLTriggerEventSuccessPayload(
+ chosen_options=["1"],
+ params_input={"input": 1},
+ responded_by_user={"id": "Fallback to defaults", "name": "Fallback to defaults"},
+ timedout=True,
+ )
)
assert mock_log.info.call_args == mock.call(
@@ -149,8 +153,7 @@ class TestHITLTrigger:
)
mock_supervisor_comms.send.return_value = HITLDetailResponse(
response_received=True,
- responded_user_id="1",
- responded_user_name="test",
+ responded_by_user=HITLUser(id="1", name="test"),
response_at=action_datetime,
chosen_options=["2"],
params_input={},
@@ -162,7 +165,12 @@ class TestHITLTrigger:
event = await trigger_task
assert event == TriggerEvent(
- HITLTriggerEventSuccessPayload(chosen_options=["2"], params_input={}, timedout=False)
+ HITLTriggerEventSuccessPayload(
+ chosen_options=["2"],
+ params_input={},
+ responded_by_user={"id": "1", "name": "test"},
+ timedout=False,
+ )
)
assert mock_log.info.call_args == mock.call(
@@ -188,8 +196,7 @@ class TestHITLTrigger:
)
mock_supervisor_comms.send.return_value = HITLDetailResponse(
response_received=True,
- responded_user_id="test",
- responded_user_name="test",
+ responded_by_user=HITLUser(id="test", name="test"),
response_at=utcnow(),
chosen_options=["3"],
params_input={"input": 50},
@@ -203,6 +210,7 @@ class TestHITLTrigger:
HITLTriggerEventSuccessPayload(
chosen_options=["3"],
params_input={"input": 50},
+ responded_by_user={"id": "test", "name": "test"},
timedout=False,
)
)
diff --git a/task-sdk/src/airflow/sdk/api/client.py b/task-sdk/src/airflow/sdk/api/client.py
index 04575fa770d..0a353271c10 100644
--- a/task-sdk/src/airflow/sdk/api/client.py
+++ b/task-sdk/src/airflow/sdk/api/client.py
@@ -44,6 +44,7 @@ from airflow.sdk.api.datamodels._generated import (
DagRunStateResponse,
DagRunType,
HITLDetailResponse,
+ HITLUser,
InactiveAssetsResponse,
PrevSuccessfulDagRunResponse,
TaskInstanceState,
@@ -723,7 +724,7 @@ class HITLOperations:
defaults: list[str] | None = None,
multiple: bool = False,
params: dict[str, Any] | None = None,
- respondents: list[str] | None = None,
+ assigned_users: list[HITLUser] | None = None,
) -> HITLDetailRequestResult:
"""Add a Human-in-the-loop response that waits for human response for a specific Task Instance."""
payload = CreateHITLDetailPayload(
@@ -734,7 +735,7 @@ class HITLOperations:
defaults=defaults,
multiple=multiple,
params=params,
- respondents=respondents,
+ assigned_users=assigned_users,
)
resp = self.client.post(
f"/hitlDetails/{ti_id}",
diff --git a/task-sdk/src/airflow/sdk/api/datamodels/_generated.py b/task-sdk/src/airflow/sdk/api/datamodels/_generated.py
index cd861bf07d1..d2151d59d02 100644
--- a/task-sdk/src/airflow/sdk/api/datamodels/_generated.py
+++ b/task-sdk/src/airflow/sdk/api/datamodels/_generated.py
@@ -154,32 +154,13 @@ class DagRunType(str, Enum):
ASSET_TRIGGERED = "asset_triggered"
-class HITLDetailRequest(BaseModel):
+class HITLUser(BaseModel):
"""
- Schema for the request part of a Human-in-the-loop detail for a specific task instance.
+ Schema for a Human-in-the-loop users.
"""
- ti_id: Annotated[UUID, Field(title="Ti Id")]
- options: Annotated[list[str], Field(min_length=1, title="Options")]
- subject: Annotated[str, Field(title="Subject")]
- body: Annotated[str | None, Field(title="Body")] = None
- defaults: Annotated[list[str] | None, Field(title="Defaults")] = None
- multiple: Annotated[bool | None, Field(title="Multiple")] = False
- params: Annotated[dict[str, Any] | None, Field(title="Params")] = None
- respondents: Annotated[list[str] | None, Field(title="Respondents")] = None
-
-
-class HITLDetailResponse(BaseModel):
- """
- Schema for the response part of a Human-in-the-loop detail for a specific task instance.
- """
-
- response_received: Annotated[bool, Field(title="Response Received")]
- responded_user_name: Annotated[str | None, Field(title="Responded User Name")] = None
- responded_user_id: Annotated[str | None, Field(title="Responded User Id")] = None
- response_at: Annotated[AwareDatetime | None, Field(title="Response At")] = None
- chosen_options: Annotated[list[str] | None, Field(title="Chosen Options")] = None
- params_input: Annotated[dict[str, Any] | None, Field(title="Params Input")] = None
+ id: Annotated[str, Field(title="Id")]
+ name: Annotated[str, Field(title="Name")]
class InactiveAssetsResponse(BaseModel):
@@ -561,6 +542,33 @@ class DagRun(BaseModel):
consumed_asset_events: Annotated[list[AssetEventDagRunReference], Field(title="Consumed Asset Events")]
+class HITLDetailRequest(BaseModel):
+ """
+ Schema for the request part of a Human-in-the-loop detail for a specific task instance.
+ """
+
+ ti_id: Annotated[UUID, Field(title="Ti Id")]
+ options: Annotated[list[str], Field(min_length=1, title="Options")]
+ subject: Annotated[str, Field(title="Subject")]
+ body: Annotated[str | None, Field(title="Body")] = None
+ defaults: Annotated[list[str] | None, Field(title="Defaults")] = None
+ 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
+
+
+class HITLDetailResponse(BaseModel):
+ """
+ Schema for the response part of a Human-in-the-loop detail for a specific task instance.
+ """
+
+ response_received: Annotated[bool, Field(title="Response Received")]
+ responded_by_user: HITLUser | None = None
+ response_at: Annotated[AwareDatetime | None, Field(title="Response At")] = None
+ chosen_options: Annotated[list[str] | None, Field(title="Chosen Options")] = None
+ params_input: Annotated[dict[str, Any] | None, Field(title="Params Input")] = None
+
+
class HTTPValidationError(BaseModel):
detail: Annotated[list[ValidationError] | None, Field(title="Detail")] = None
diff --git a/task-sdk/src/airflow/sdk/execution_time/hitl.py b/task-sdk/src/airflow/sdk/execution_time/hitl.py
index 500be78ab4e..07f94e63c05 100644
--- a/task-sdk/src/airflow/sdk/execution_time/hitl.py
+++ b/task-sdk/src/airflow/sdk/execution_time/hitl.py
@@ -17,9 +17,10 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, TypedDict
from uuid import UUID
+from airflow.sdk.api.datamodels._generated import HITLUser as APIHITLUser
from airflow.sdk.execution_time.comms import (
CreateHITLDetailPayload,
GetHITLDetailResponse,
@@ -30,6 +31,11 @@ if TYPE_CHECKING:
from airflow.sdk.api.datamodels._generated import HITLDetailResponse
+class HITLUser(TypedDict):
+ id: str
+ name: str
+
+
def upsert_hitl_detail(
ti_id: UUID,
options: list[str],
@@ -38,7 +44,7 @@ def upsert_hitl_detail(
defaults: list[str] | None = None,
multiple: bool = False,
params: dict[str, Any] | None = None,
- respondents: list[str] | None = None,
+ assigned_users: list[HITLUser] | None = None,
) -> None:
from airflow.sdk.execution_time.task_runner import SUPERVISOR_COMMS
@@ -51,7 +57,11 @@ def upsert_hitl_detail(
defaults=defaults,
params=params,
multiple=multiple,
- respondents=respondents,
+ assigned_users=(
+ [APIHITLUser(id=user["id"], name=user["name"]) for user in assigned_users]
+ if assigned_users
+ else []
+ ),
)
)
diff --git a/task-sdk/src/airflow/sdk/execution_time/supervisor.py b/task-sdk/src/airflow/sdk/execution_time/supervisor.py
index 76a52acd98f..4969ed8a1d4 100644
--- a/task-sdk/src/airflow/sdk/execution_time/supervisor.py
+++ b/task-sdk/src/airflow/sdk/execution_time/supervisor.py
@@ -1344,7 +1344,7 @@ class ActivitySubprocess(WatchedSubprocess):
defaults=msg.defaults,
params=msg.params,
multiple=msg.multiple,
- respondents=msg.respondents,
+ assigned_users=msg.assigned_users,
)
self.send_msg(resp, request_id=req_id, error=None, **dump_opts)
elif isinstance(msg, MaskSecret):
diff --git a/task-sdk/tests/task_sdk/api/test_client.py b/task-sdk/tests/task_sdk/api/test_client.py
index 986ab5fe5e8..9464296934d 100644
--- a/task-sdk/tests/task_sdk/api/test_client.py
+++ b/task-sdk/tests/task_sdk/api/test_client.py
@@ -38,6 +38,7 @@ from airflow.sdk.api.datamodels._generated import (
DagRunState,
DagRunStateResponse,
HITLDetailResponse,
+ HITLUser,
VariableResponse,
XComResponse,
)
@@ -1289,7 +1290,7 @@ class TestHITLOperations:
assert result.defaults == ["Approval"]
assert result.params is None
assert result.multiple is False
- assert result.respondents is None
+ assert result.assigned_users is None
def test_update_response(self, time_machine: TimeMachineFixture) -> None:
time_machine.move_to(datetime(2025, 7, 3, 0, 0, 0))
@@ -1302,8 +1303,7 @@ class TestHITLOperations:
json={
"chosen_options": ["Approval"],
"params_input": {},
- "responded_user_id": "admin",
- "responded_user_name": "admin",
+ "responded_by_user": {"id": "admin", "name": "admin"},
"response_received": True,
"response_at": "2025-07-03T00:00:00Z",
},
@@ -1320,8 +1320,7 @@ class TestHITLOperations:
assert result.response_received is True
assert result.chosen_options == ["Approval"]
assert result.params_input == {}
- assert result.responded_user_id == "admin"
- assert result.responded_user_name == "admin"
+ assert result.responded_by_user == HITLUser(id="admin", name="admin")
assert result.response_at == timezone.datetime(2025, 7, 3, 0, 0, 0)
def test_get_detail_response(self, time_machine: TimeMachineFixture) -> None:
@@ -1335,8 +1334,7 @@ class TestHITLOperations:
json={
"chosen_options": ["Approval"],
"params_input": {},
- "responded_user_id": "admin",
- "responded_user_name": "admin",
+ "responded_by_user": {"id": "admin", "name": "admin"},
"response_received": True,
"response_at": "2025-07-03T00:00:00Z",
},
@@ -1349,6 +1347,5 @@ class TestHITLOperations:
assert result.response_received is True
assert result.chosen_options == ["Approval"]
assert result.params_input == {}
- assert result.responded_user_id == "admin"
- assert result.responded_user_name == "admin"
+ assert result.responded_by_user == HITLUser(id="admin", name="admin")
assert result.response_at == timezone.datetime(2025, 7, 3, 0, 0, 0)
diff --git a/task-sdk/tests/task_sdk/execution_time/test_hitl.py b/task-sdk/tests/task_sdk/execution_time/test_hitl.py
index a5ed016f44d..ad74ade5cd2 100644
--- a/task-sdk/tests/task_sdk/execution_time/test_hitl.py
+++ b/task-sdk/tests/task_sdk/execution_time/test_hitl.py
@@ -20,9 +20,10 @@ from __future__ import annotations
from uuid6 import uuid7
from airflow.sdk import timezone
-from airflow.sdk.api.datamodels._generated import HITLDetailResponse
+from airflow.sdk.api.datamodels._generated import HITLDetailResponse, HITLUser as APIHITLUser
from airflow.sdk.execution_time.comms import CreateHITLDetailPayload
from airflow.sdk.execution_time.hitl import (
+ HITLUser,
get_hitl_detail_content_detail,
update_hitl_detail_response,
upsert_hitl_detail,
@@ -39,7 +40,7 @@ def test_upsert_hitl_detail(mock_supervisor_comms) -> None:
body="Optional body",
defaults=["Approve", "Reject"],
params={"input_1": 1},
- respondents=["test"],
+ assigned_users=[HITLUser(id="test", name="test")],
multiple=False,
)
mock_supervisor_comms.send.assert_called_with(
@@ -50,7 +51,7 @@ def test_upsert_hitl_detail(mock_supervisor_comms) -> None:
body="Optional body",
defaults=["Approve", "Reject"],
params={"input_1": 1},
- respondents=["test"],
+ assigned_users=[APIHITLUser(id="test", name="test")],
multiple=False,
)
)
@@ -62,8 +63,7 @@ def test_update_hitl_detail_response(mock_supervisor_comms) -> None:
response_received=True,
chosen_options=["Approve"],
response_at=timestamp,
- responded_user_id="admin",
- responded_user_name="admin",
+ responded_by_user=APIHITLUser(id="admin", name="admin"),
params_input={"input_1": 1},
)
resp = update_hitl_detail_response(
@@ -75,8 +75,7 @@ def test_update_hitl_detail_response(mock_supervisor_comms) -> None:
response_received=True,
chosen_options=["Approve"],
response_at=timestamp,
- responded_user_id="admin",
- responded_user_name="admin",
+ responded_by_user=APIHITLUser(id="admin", name="admin"),
params_input={"input_1": 1},
)
@@ -86,8 +85,7 @@ def test_get_hitl_detail_content_detail(mock_supervisor_comms) -> None:
response_received=False,
chosen_options=None,
response_at=None,
- responded_user_id=None,
- responded_user_name=None,
+ responded_by_user=None,
params_input={},
)
resp = get_hitl_detail_content_detail(TI_ID)
@@ -95,7 +93,6 @@ def test_get_hitl_detail_content_detail(mock_supervisor_comms) -> None:
response_received=False,
chosen_options=None,
response_at=None,
- responded_user_id=None,
- responded_user_name=None,
+ responded_by_user=None,
params_input={},
)
diff --git a/task-sdk/tests/task_sdk/execution_time/test_supervisor.py b/task-sdk/tests/task_sdk/execution_time/test_supervisor.py
index ce60753bf6a..ae39595197a 100644
--- a/task-sdk/tests/task_sdk/execution_time/test_supervisor.py
+++ b/task-sdk/tests/task_sdk/execution_time/test_supervisor.py
@@ -2008,7 +2008,7 @@ REQUEST_TEST_CASES = [
"defaults": ["Approve"],
"multiple": False,
"params": {},
- "respondents": None,
+ "assigned_users": None,
"type": "HITLDetailRequestResult",
},
client_mock=ClientMock(
@@ -2019,7 +2019,7 @@ REQUEST_TEST_CASES = [
"multiple": False,
"options": ["Approve", "Reject"],
"params": {},
- "respondents": None,
+ "assigned_users": None,
"subject": "This is subject",
"ti_id": TI_ID,
},
(airflow) 08/22: Bump axios (#55552)
Posted by ka...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
kaxilnaik pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 835a8636b97c4b82eb9fc5a96fb93ae9a84617b4
Author: Dependabot [bot] <49...@users.noreply.github.com>
AuthorDate: Fri Sep 12 14:51:52 2025 -0400
Bump axios (#55552)
Bumps [axios](https://github.com/axios/axios) from 1.11.0 to 1.12.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.11.0...v1.12.0)
---
updated-dependencies:
- dependency-name: axios
dependency-version: 1.12.0
dependency-type: direct:production
...
Signed-off-by: dependabot[bot] <su...@github.com>
Co-authored-by: dependabot[bot] <49...@users.noreply.github.com>
(cherry picked from commit 800db4f862d58da53817b36d2d2fc2ba2570bbdf)
---
.../auth/managers/simple/ui/package-lock.json | 8 +-
.../auth/managers/simple/ui/package.json | 2 +-
.../auth/managers/simple/ui/pnpm-lock.yaml | 110 ++++++++++-----------
3 files changed, 60 insertions(+), 60 deletions(-)
diff --git a/airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/package-lock.json b/airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/package-lock.json
index 18780182ff4..5c2a403d1e1 100644
--- a/airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/package-lock.json
+++ b/airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/package-lock.json
@@ -10,7 +10,7 @@
"dependencies": {
"@chakra-ui/react": "^3.25.0",
"@tanstack/react-query": "^5.85.5",
- "axios": "^1.11.0",
+ "axios": "^1.12.0",
"next-themes": "^0.4.6",
"react": "^19.1.1",
"react-cookie": "^8.0.1",
@@ -3669,9 +3669,9 @@
}
},
"node_modules/axios": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
- "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz",
+ "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
diff --git a/airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/package.json b/airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/package.json
index dd681473cfd..4867439f683 100644
--- a/airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/package.json
+++ b/airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/package.json
@@ -17,7 +17,7 @@
"dependencies": {
"@chakra-ui/react": "^3.25.0",
"@tanstack/react-query": "^5.85.5",
- "axios": "^1.11.0",
+ "axios": "^1.12.0",
"next-themes": "^0.4.6",
"react": "^19.1.1",
"react-cookie": "^8.0.1",
diff --git a/airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/pnpm-lock.yaml b/airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/pnpm-lock.yaml
index 7621169aa6a..0f6e6c66950 100644
--- a/airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/pnpm-lock.yaml
+++ b/airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/pnpm-lock.yaml
@@ -15,8 +15,8 @@ importers:
specifier: ^5.85.5
version: 5.85.5(react@19.1.1)
axios:
- specifier: ^1.11.0
- version: 1.11.0
+ specifier: ^1.12.0
+ version: 1.12.0
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -173,8 +173,8 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
- '@babel/parser@7.28.3':
- resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==}
+ '@babel/parser@7.28.4':
+ resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -182,8 +182,8 @@ packages:
resolution: {integrity: sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==}
engines: {node: '>=6.9.0'}
- '@babel/runtime@7.28.3':
- resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==}
+ '@babel/runtime@7.28.4':
+ resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
engines: {node: '>=6.9.0'}
'@babel/template@7.27.1':
@@ -198,16 +198,16 @@ packages:
resolution: {integrity: sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==}
engines: {node: '>=6.9.0'}
- '@babel/traverse@7.28.3':
- resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==}
+ '@babel/traverse@7.28.4':
+ resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==}
engines: {node: '>=6.9.0'}
'@babel/types@7.27.1':
resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==}
engines: {node: '>=6.9.0'}
- '@babel/types@7.28.2':
- resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==}
+ '@babel/types@7.28.4':
+ resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
engines: {node: '>=6.9.0'}
'@chakra-ui/react@3.25.0':
@@ -538,8 +538,8 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
- '@jridgewell/trace-mapping@0.3.30':
- resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==}
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@jsdevtools/ono@7.1.3':
resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==}
@@ -1203,8 +1203,8 @@ packages:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
- ansi-regex@6.2.0:
- resolution: {integrity: sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==}
+ ansi-regex@6.2.2:
+ resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
engines: {node: '>=12'}
ansi-styles@4.3.0:
@@ -1215,8 +1215,8 @@ packages:
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
engines: {node: '>=10'}
- ansi-styles@6.2.1:
- resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
+ ansi-styles@6.2.3:
+ resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==}
engines: {node: '>=12'}
anymatch@3.1.3:
@@ -1283,8 +1283,8 @@ packages:
resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==}
engines: {node: '>=4'}
- axios@1.11.0:
- resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==}
+ axios@1.12.0:
+ resolution: {integrity: sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==}
axobject-query@4.1.0:
resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
@@ -1740,8 +1740,8 @@ packages:
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
- follow-redirects@1.15.9:
- resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
+ follow-redirects@1.15.11:
+ resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
@@ -2594,8 +2594,8 @@ packages:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
- strip-ansi@7.1.0:
- resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
+ strip-ansi@7.1.2:
+ resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
engines: {node: '>=12'}
strip-indent@3.0.0:
@@ -2967,18 +2967,18 @@ snapshots:
'@babel/generator@7.28.3':
dependencies:
- '@babel/parser': 7.28.3
- '@babel/types': 7.28.2
+ '@babel/parser': 7.28.4
+ '@babel/types': 7.28.4
'@jridgewell/gen-mapping': 0.3.13
- '@jridgewell/trace-mapping': 0.3.30
+ '@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
'@babel/helper-globals@7.28.0': {}
'@babel/helper-module-imports@7.27.1':
dependencies:
- '@babel/traverse': 7.28.3
- '@babel/types': 7.28.2
+ '@babel/traverse': 7.28.4
+ '@babel/types': 7.28.4
transitivePeerDependencies:
- supports-color
@@ -2990,13 +2990,13 @@ snapshots:
dependencies:
'@babel/types': 7.27.1
- '@babel/parser@7.28.3':
+ '@babel/parser@7.28.4':
dependencies:
- '@babel/types': 7.28.2
+ '@babel/types': 7.28.4
'@babel/runtime@7.27.1': {}
- '@babel/runtime@7.28.3': {}
+ '@babel/runtime@7.28.4': {}
'@babel/template@7.27.1':
dependencies:
@@ -3007,8 +3007,8 @@ snapshots:
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
- '@babel/parser': 7.28.3
- '@babel/types': 7.28.2
+ '@babel/parser': 7.28.4
+ '@babel/types': 7.28.4
'@babel/traverse@7.27.1':
dependencies:
@@ -3022,14 +3022,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/traverse@7.28.3':
+ '@babel/traverse@7.28.4':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/generator': 7.28.3
'@babel/helper-globals': 7.28.0
- '@babel/parser': 7.28.3
+ '@babel/parser': 7.28.4
'@babel/template': 7.27.2
- '@babel/types': 7.28.2
+ '@babel/types': 7.28.4
debug: 4.4.1
transitivePeerDependencies:
- supports-color
@@ -3039,7 +3039,7 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
- '@babel/types@7.28.2':
+ '@babel/types@7.28.4':
dependencies:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
@@ -3061,7 +3061,7 @@ snapshots:
'@emotion/babel-plugin@11.13.5':
dependencies:
'@babel/helper-module-imports': 7.27.1
- '@babel/runtime': 7.28.3
+ '@babel/runtime': 7.28.4
'@emotion/hash': 0.9.2
'@emotion/memoize': 0.9.0
'@emotion/serialize': 1.3.3
@@ -3092,7 +3092,7 @@ snapshots:
'@emotion/react@11.14.0(@types/react@19.1.11)(react@19.1.1)':
dependencies:
- '@babel/runtime': 7.28.3
+ '@babel/runtime': 7.28.4
'@emotion/babel-plugin': 11.13.5
'@emotion/cache': 11.14.0
'@emotion/serialize': 1.3.3
@@ -3304,7 +3304,7 @@ snapshots:
dependencies:
string-width: 5.1.2
string-width-cjs: string-width@4.2.3
- strip-ansi: 7.1.0
+ strip-ansi: 7.1.2
strip-ansi-cjs: strip-ansi@6.0.1
wrap-ansi: 8.1.0
wrap-ansi-cjs: wrap-ansi@7.0.0
@@ -3312,7 +3312,7 @@ snapshots:
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
- '@jridgewell/trace-mapping': 0.3.30
+ '@jridgewell/trace-mapping': 0.3.31
'@jridgewell/gen-mapping@0.3.8':
dependencies:
@@ -3333,7 +3333,7 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
- '@jridgewell/trace-mapping@0.3.30':
+ '@jridgewell/trace-mapping@0.3.31':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
@@ -3498,7 +3498,7 @@ snapshots:
'@testing-library/dom@10.4.0':
dependencies:
'@babel/code-frame': 7.27.1
- '@babel/runtime': 7.28.3
+ '@babel/runtime': 7.28.4
'@types/aria-query': 5.0.4
aria-query: 5.3.0
chalk: 4.1.2
@@ -4296,7 +4296,7 @@ snapshots:
ansi-regex@5.0.1: {}
- ansi-regex@6.2.0: {}
+ ansi-regex@6.2.2: {}
ansi-styles@4.3.0:
dependencies:
@@ -4304,7 +4304,7 @@ snapshots:
ansi-styles@5.2.0: {}
- ansi-styles@6.2.1: {}
+ ansi-styles@6.2.3: {}
anymatch@3.1.3:
dependencies:
@@ -4388,9 +4388,9 @@ snapshots:
axe-core@4.10.3: {}
- axios@1.11.0:
+ axios@1.12.0:
dependencies:
- follow-redirects: 1.15.9
+ follow-redirects: 1.15.11
form-data: 4.0.4
proxy-from-env: 1.1.0
transitivePeerDependencies:
@@ -4400,7 +4400,7 @@ snapshots:
babel-plugin-macros@3.1.0:
dependencies:
- '@babel/runtime': 7.28.3
+ '@babel/runtime': 7.28.4
cosmiconfig: 7.1.0
resolve: 1.22.10
@@ -4989,7 +4989,7 @@ snapshots:
flatted@3.3.3: {}
- follow-redirects@1.15.9: {}
+ follow-redirects@1.15.11: {}
for-each@0.3.5:
dependencies:
@@ -5364,8 +5364,8 @@ snapshots:
magicast@0.3.5:
dependencies:
- '@babel/parser': 7.28.3
- '@babel/types': 7.28.2
+ '@babel/parser': 7.28.4
+ '@babel/types': 7.28.4
source-map-js: 1.2.1
optional: true
@@ -5834,7 +5834,7 @@ snapshots:
dependencies:
eastasianwidth: 0.2.0
emoji-regex: 9.2.2
- strip-ansi: 7.1.0
+ strip-ansi: 7.1.2
string.prototype.includes@2.0.1:
dependencies:
@@ -5890,9 +5890,9 @@ snapshots:
dependencies:
ansi-regex: 5.0.1
- strip-ansi@7.1.0:
+ strip-ansi@7.1.2:
dependencies:
- ansi-regex: 6.2.0
+ ansi-regex: 6.2.2
strip-indent@3.0.0:
dependencies:
@@ -6183,9 +6183,9 @@ snapshots:
wrap-ansi@8.1.0:
dependencies:
- ansi-styles: 6.2.1
+ ansi-styles: 6.2.3
string-width: 5.1.2
- strip-ansi: 7.1.0
+ strip-ansi: 7.1.2
yallist@4.0.0: {}