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

pierrejeambrun pushed a commit to branch v3-0-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-0-test by this push:
     new ff4efef851f [v3-0-test] Structure endpoint attach downstream asset to 
task level (#51401) (#51425)
ff4efef851f is described below

commit ff4efef851f101ea6333b21880612d73806e22c5
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Thu Jun 5 15:47:44 2025 +0200

    [v3-0-test] Structure endpoint attach downstream asset to task level 
(#51401) (#51425)
    
    * Attach downstream assets to task
    
    * Adjust tests
    (cherry picked from commit 14ee1f4f60019b4ff90f09ede1d10abf42feea4d)
    
    Co-authored-by: Pierre Jeambrun <[email protected]>
---
 .../api_fastapi/core_api/routes/ui/structure.py    |  7 ++++-
 .../api_fastapi/core_api/services/ui/structure.py  | 31 ++++++++++++++++++++++
 .../core_api/routes/ui/test_structure.py           |  2 +-
 3 files changed, 38 insertions(+), 2 deletions(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/structure.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/structure.py
index 05fb79bd0bf..25c8134203b 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/structure.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/structure.py
@@ -26,7 +26,10 @@ from airflow.api_fastapi.common.router import AirflowRouter
 from airflow.api_fastapi.core_api.datamodels.ui.structure import 
StructureDataResponse
 from airflow.api_fastapi.core_api.openapi.exceptions import 
create_openapi_http_exception_doc
 from airflow.api_fastapi.core_api.security import requires_access_dag
-from airflow.api_fastapi.core_api.services.ui.structure import 
get_upstream_assets
+from airflow.api_fastapi.core_api.services.ui.structure import (
+    bind_output_assets_to_tasks,
+    get_upstream_assets,
+)
 from airflow.models.dag_version import DagVersion
 from airflow.models.serialized_dag import SerializedDagModel
 from airflow.utils.dag_edges import dag_edges
@@ -139,4 +142,6 @@ def structure_data(
 
         data["edges"] += start_edges + edges + end_edges
 
+    bind_output_assets_to_tasks(data["edges"], serialized_dag)
+
     return StructureDataResponse(**data)
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/services/ui/structure.py 
b/airflow-core/src/airflow/api_fastapi/core_api/services/ui/structure.py
index 128dc93b770..6f5f415d3fd 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/services/ui/structure.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/services/ui/structure.py
@@ -23,6 +23,8 @@ Private service for dag structure.
 
 from __future__ import annotations
 
+from airflow.models.serialized_dag import SerializedDagModel
+
 
 def get_upstream_assets(
     asset_expression: dict, entry_node_ref: str, level: int = 0
@@ -112,3 +114,32 @@ def get_upstream_assets(
             edges = edges + e
 
     return nodes, edges
+
+
+def bind_output_assets_to_tasks(edges: list[dict], serialized_dag: 
SerializedDagModel) -> None:
+    """
+    Try to bind the downstream assets to the relevant task that produces them.
+
+    This function will mutate the `edges` in place.
+    """
+    outlet_asset_references = 
serialized_dag.dag_model.task_outlet_asset_references
+
+    downstream_asset_related_edges = [edge for edge in edges if 
edge["target_id"].startswith("asset:")]
+
+    for edge in downstream_asset_related_edges:
+        asset_id = int(edge["target_id"].strip("asset:"))
+        try:
+            # Try to attach the outlet asset to the relevant task
+            outlet_asset_reference = next(
+                outlet_asset_reference
+                for outlet_asset_reference in outlet_asset_references
+                if outlet_asset_reference.asset_id == asset_id
+            )
+            edge["source_id"] = outlet_asset_reference.task_id
+            continue
+        except StopIteration:
+            # If no asset reference found, fallback to using the exit node 
reference
+            # This can happen because asset aliases are not yet handled, they 
do no populate
+            # the `outlet_asset_references` when resolved. Extra lookup is 
needed. Same for asset-name-ref and
+            # asset-uri-ref.
+            pass
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_structure.py 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_structure.py
index 928c0b2cc84..035721a1dcb 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_structure.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_structure.py
@@ -369,7 +369,7 @@ class TestStructureDataEndpoint:
                 {
                     "is_setup_teardown": None,
                     "label": None,
-                    "source_id": "task_2",
+                    "source_id": "task_1",
                     "target_id": f"asset:{asset3_id}",
                     "is_source_asset": None,
                 },

Reply via email to