This is an automated email from the ASF dual-hosted git repository.

kaxil pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new d0450666eb3 Remove a Dag Run or Task Instance note when its content is 
cleared (#69033)
d0450666eb3 is described below

commit d0450666eb377f8f1cfad0d7addfbffa61768da8
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Sat Jun 27 02:21:08 2026 +0200

    Remove a Dag Run or Task Instance note when its content is cleared (#69033)
    
    There was no way to remove a note from the UI: emptying the text persisted
    an empty note and the Dag Run or Task Instance stayed flagged as having one.
    An empty note now unsets it, while a null note keeps leaving the existing
    note untouched so the bulk and clear flows are unaffected.
---
 .../api_fastapi/core_api/services/public/dag_run.py      |  6 ++++--
 .../core_api/services/public/task_instances.py           |  4 +++-
 .../api_fastapi/core_api/routes/public/test_dag_run.py   | 16 ++++++++++++++--
 .../core_api/routes/public/test_task_instances.py        | 14 ++++++++++++++
 4 files changed, 35 insertions(+), 5 deletions(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/services/public/dag_run.py 
b/airflow-core/src/airflow/api_fastapi/core_api/services/public/dag_run.py
index 6d43d18cd34..186cd3671c6 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/services/public/dag_run.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/services/public/dag_run.py
@@ -212,8 +212,10 @@ def patch_dag_run_state(
 
 
 def patch_dag_run_note(*, dag_run: DagRun, note: str | None, user: BaseUser) 
-> None:
-    """Set or update a Dag Run's note."""
-    if dag_run.dag_run_note is None:
+    """Set, update, or clear a Dag Run's note. An empty note removes it so the 
run is left without a note."""
+    if note == "":
+        dag_run.dag_run_note = None
+    elif dag_run.dag_run_note is None:
         dag_run.note = (note, user.get_id())
     else:
         dag_run.dag_run_note.content = note
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/services/public/task_instances.py
 
b/airflow-core/src/airflow/api_fastapi/core_api/services/public/task_instances.py
index a5874035e27..24ae587680a 100644
--- 
a/airflow-core/src/airflow/api_fastapi/core_api/services/public/task_instances.py
+++ 
b/airflow-core/src/airflow/api_fastapi/core_api/services/public/task_instances.py
@@ -315,7 +315,9 @@ def _patch_task_instance_note(
 ) -> None:
     for ti in tis:
         if update_mask or task_instance_body.note is not None:
-            if ti.task_instance_note is None:
+            if task_instance_body.note == "":
+                ti.task_instance_note = None
+            elif ti.task_instance_note is None:
                 ti.note = (task_instance_body.note, user.get_id())
             else:
                 ti.task_instance_note.content = task_instance_body.note
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_run.py 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_run.py
index 39a391f80f1..fe7c2739de2 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_run.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_run.py
@@ -1440,6 +1440,13 @@ class TestPatchDagRun:
                 {"state": DagRunState.SUCCESS, "note": "updated note"},
                 {"user_id": "test", "content": "updated note"},
             ),
+            (
+                DAG1_ID,
+                DAG1_RUN1_ID,
+                {"note": ""},
+                {"state": DagRunState.SUCCESS, "note": None},
+                None,
+            ),
             (
                 DAG1_ID,
                 DAG1_RUN2_ID,
@@ -1758,11 +1765,16 @@ class TestClearDagRun:
         ("body", "expected_note"),
         [
             ({"dry_run": False, "note": "cleared by test"}, "cleared by test"),
-            ({"dry_run": False, "note": ""}, ""),
+            ({"dry_run": False, "note": ""}, None),
             ({"dry_run": False, "note": None}, "test_note"),
             ({"dry_run": False}, "test_note"),
         ],
-        ids=["set-new-note", "set-empty-note", 
"explicit-null-leaves-existing", "omit-leaves-existing"],
+        ids=[
+            "set-new-note",
+            "empty-note-removes-existing",
+            "explicit-null-leaves-existing",
+            "omit-leaves-existing",
+        ],
     )
     @pytest.mark.usefixtures("configure_git_connection_for_dag_bundle")
     def test_clear_dag_run_applies_note(self, test_client, session, body, 
expected_note):
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_task_instances.py
 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_task_instances.py
index 40337bb9be7..11f69edb6f6 100644
--- 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_task_instances.py
+++ 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_task_instances.py
@@ -5128,6 +5128,20 @@ class TestPatchTaskInstance(TestTaskInstanceEndpoint):
             session, response_data["task_instances"][0]["id"], {"content": 
new_note_value, "user_id": "test"}
         )
 
+    def test_set_empty_note_removes_existing_note(self, test_client, session):
+        self.create_task_instances(session)
+        url = 
"/dags/example_python_operator/dagRuns/TEST_DAG_RUN_ID/taskInstances/print_the_context"
+
+        set_response = test_client.patch(url, json={"note": "a note to 
remove"})
+        assert set_response.status_code == 200, set_response.text
+        ti_id = set_response.json()["task_instances"][0]["id"]
+        _check_task_instance_note(session, ti_id, {"content": "a note to 
remove", "user_id": "test"})
+
+        clear_response = test_client.patch(url, json={"note": ""})
+        assert clear_response.status_code == 200, clear_response.text
+        assert clear_response.json()["task_instances"][0]["note"] is None
+        _check_task_instance_note(session, ti_id, None)
+
     def test_set_note_should_respond_200_mapped_task_with_rtif(self, 
test_client, session):
         """Verify we don't duplicate rows through join to RTIF"""
         tis = self.create_task_instances(session)

Reply via email to