1010
1111from arus .shared .db .session import get_db
1212from arus .modules .auth .router import get_current_user
13- from arus .modules .run_log .models import Run
13+ from arus .modules .run_log .models import Run , RunTableStat
1414from arus .modules .pipeline .models import Pipeline , PipelineTable
1515from arus .modules .source .models import Source
16+ from arus .modules .destination .models import Destination
1617
1718router = APIRouter (prefix = "/api/dag" , tags = ["dag" ])
1819
@@ -28,19 +29,40 @@ async def get_dag_data(
2829
2930 for p in pipelines :
3031 source = db .query (Source ).filter (Source .id == p .source_id ).first ()
32+ destination = db .query (Destination ).filter (Destination .id == p .destination_id ).first ()
3133 tables = db .query (PipelineTable ).filter (
3234 PipelineTable .pipeline_id == p .id , PipelineTable .enabled == True
3335 ).all ()
3436
35- # Get last run status per table
37+ # Get last run and per- table stats
3638 last_run = (
3739 db .query (Run )
3840 .filter (Run .pipeline_id == str (p .id ))
3941 .order_by (desc (Run .started_at ))
4042 .first ()
4143 )
42-
4344 pipeline_status = last_run .status if last_run else "not_started"
45+ currently_running = last_run and last_run .status == "running"
46+
47+ # Get per-table stats from the last completed run
48+ table_stats = {}
49+ if last_run :
50+ stats = db .query (RunTableStat ).filter (RunTableStat .run_id == last_run .id ).all ()
51+ for s in stats :
52+ table_stats [s .table_name ] = s
53+
54+ def asset_status (table_name ):
55+ """Derive per-asset status. If currently running, all show 'running'.
56+ Otherwise check per-table stat, fallback to pipeline-level status."""
57+ if currently_running :
58+ return "running"
59+ stat = table_stats .get (table_name )
60+ if stat :
61+ if stat .error_message :
62+ return "failed"
63+ return "success"
64+ return pipeline_status
65+
4466 safe_name = source .name .lower ().replace ("-" , "_" ).replace (" " , "_" ) if source else "unknown"
4567
4668 assets = []
@@ -49,40 +71,80 @@ async def get_dag_data(
4971
5072 for t in tables :
5173 load_mode = getattr (t , "load_mode" , None ) or p .load_mode or "direct"
74+ status = asset_status (t .source_table )
75+ stat = table_stats .get (t .source_table )
5276
53- # Source asset always exists
77+ # ---- Source asset ----
5478 assets .append ({
5579 "name" : t .source_table ,
5680 "layer" : "source" ,
57- "status" : pipeline_status ,
81+ "status" : status ,
5882 "table" : t .source_table ,
83+ "schema" : t .source_schema ,
84+ "sync_mode" : t .sync_mode ,
85+ "source_type" : source .type if source else None ,
86+ "source_host" : source .host if source else None ,
87+ "source_port" : source .port if source else None ,
88+ "source_database" : source .database if source else None ,
89+ "rows" : stat .rows_extracted if stat else 0 ,
90+ "duration_ms" : stat .duration_ms if stat else 0 ,
91+ "error" : stat .error_message if stat else None ,
5992 })
6093
6194 if load_mode == "raw" :
62- # Raw mode: source → raw → target
95+ # ---- Raw mode: source → raw → target ----
6396 has_raw = True
6497 raw_name = f"{ safe_name } _{ t .source_table } _raw"
98+ raw_status = asset_status (raw_name )
99+ raw_stat = table_stats .get (raw_name )
100+
65101 assets .append ({
66102 "name" : raw_name ,
67103 "layer" : "raw" ,
68- "status" : pipeline_status ,
104+ "status" : raw_status ,
69105 "table" : t .source_table ,
106+ "schema" : t .source_schema ,
107+ "sync_mode" : t .sync_mode ,
108+ "raw_table" : raw_name ,
109+ "raw_schema" : destination .raw_schema if destination else "staging" ,
110+ "rows" : raw_stat .rows_loaded_raw if raw_stat else 0 ,
111+ "duration_ms" : raw_stat .duration_ms if raw_stat else 0 ,
112+ "error" : raw_stat .error_message if raw_stat else None ,
70113 })
114+
115+ target_status = asset_status (t .source_table )
116+ target_stat = table_stats .get (t .source_table )
117+
71118 assets .append ({
72119 "name" : t .source_table ,
73120 "layer" : "target" ,
74- "status" : pipeline_status ,
121+ "status" : target_status ,
75122 "table" : t .source_table ,
123+ "schema" : t .source_schema ,
124+ "sync_mode" : t .sync_mode ,
125+ "target_table" : t .source_table ,
126+ "target_schema" : t .target_schema or (destination .target_schema if destination else None ),
127+ "rows" : target_stat .rows_loaded_analytics if target_stat else 0 ,
128+ "duration_ms" : target_stat .duration_ms if target_stat else 0 ,
129+ "error" : target_stat .error_message if target_stat else None ,
76130 })
131+
77132 edges .append ({"from" : t .source_table , "to" : raw_name })
78133 edges .append ({"from" : raw_name , "to" : t .source_table })
79134 else :
80- # Direct mode: source → target
135+ # ---- Direct mode: source → target ----
81136 assets .append ({
82137 "name" : t .source_table ,
83138 "layer" : "target" ,
84- "status" : pipeline_status ,
139+ "status" : status ,
85140 "table" : t .source_table ,
141+ "schema" : t .source_schema ,
142+ "sync_mode" : t .sync_mode ,
143+ "target_table" : t .source_table ,
144+ "target_schema" : t .target_schema or (destination .target_schema if destination else None ),
145+ "rows" : stat .rows_loaded_analytics if stat else 0 ,
146+ "duration_ms" : stat .duration_ms if stat else 0 ,
147+ "error" : stat .error_message if stat else None ,
86148 })
87149 edges .append ({"from" : t .source_table , "to" : t .source_table })
88150
0 commit comments