@@ -710,10 +710,13 @@ def build_exception_section(p: dlt.Pipeline) -> List[Any]:
710710 if not exception_step :
711711 return []
712712
713+ last_exception = exception_step .exception_traces [- 1 ]
714+ title = f"{ last_exception ['exception_type' ]} : { last_exception ['message' ]} "
715+
713716 _result = []
714717 _result .append (
715718 ui .build_title_and_subtitle (
716- f"Exception encountered during last pipeline run in step ' { step . step } '" ,
719+ title ,
717720 title_level = 2 ,
718721 )
719722 )
@@ -857,19 +860,36 @@ def _format_duration(ms: float) -> str:
857860 return f"{ round (ms / 6000 ) / 10 } "
858861
859862
863+ def _build_migration_badge (count : int ) -> str :
864+ """Build migration badge HTML using CSS classes"""
865+ if count == 0 :
866+ return ""
867+ return (
868+ '<div class="status-badge status-badge-yellow">'
869+ f"<strong>{ count } dataset migration(s)</strong>"
870+ "</div>"
871+ )
872+
873+
874+ def _build_status_badge (status : TPipelineRunStatus ) -> str :
875+ """Build status badge HTML using CSS classes"""
876+ badge_class = "status-badge-green" if status == "succeeded" else "status-badge-red"
877+ return f'<div class="status-badge { badge_class } "><strong>{ status } </strong></div>'
878+
879+
860880def _build_pipeline_execution_html (
861881 transaction_id : str ,
862882 status : TPipelineRunStatus ,
863883 steps_data : List [PipelineStepData ],
864884 migrations_count : int = 0 ,
865885) -> mo .Html :
866886 """
867- Build an HTML visualization for a pipeline execution
887+ Build an HTML visualization for a pipeline execution using CSS classes
868888 """
869889 total_ms = sum (step .duration_ms for step in steps_data )
870890 last = len (steps_data ) - 1
871891
872- # Build the general info of the exection
892+ # Build the general info of the execution
873893 general_info = f"""
874894 <div>Last execution ID: <strong>{ transaction_id [:8 ]} </strong></div>
875895 <div>Total time: <strong>{ _format_duration (total_ms )} </strong></div>
@@ -880,98 +900,45 @@ def _build_pipeline_execution_html(
880900 for i , step in enumerate (steps_data ):
881901 percentage = step .duration_ms / total_ms * 100
882902 color = PIPELINE_RUN_STEP_COLORS .get (step .step )
883- radius = "6px 0 0 6px" if i == 0 else ("0 6px 6px 0" if i == last else "0" )
884-
903+ radius = (
904+ "6px"
905+ if i == 0 and i == last
906+ else "6px 0 0 6px" if i == 0 else "0 6px 6px 0" if i == last else "0"
907+ )
885908 segments .append (
886- " <div"
887- f' style="width:{ percentage } %;background-color:{ color } ;border-radius:{ radius } ;"></div>'
909+ ' <div class="pipeline-execution-timeline-segment" '
910+ f'style="width:{ percentage } %;background-color:{ color } ;border-radius:{ radius } ;"></div>'
888911 )
889912 labels .append (
890913 f'<span><span style="color:{ color } ;">●</span> '
891914 f"{ step .step .capitalize ()} { _format_duration (step .duration_ms )} </span>"
892915 )
893- segments_bar = f"""
894- <div style="
895- display: flex;
896- flex-direction: row;
897- justify-content: center;
898- height: 16px;
899- ">{ '' .join (segments )} </div>
900- """
901- segment_labels = f"""
902- <div style="
903- display: flex;
904- flex-direction: row;
905- justify-content: space-between;
906- ">{ '' .join (labels )} </div>
907- """
908916
909- # Build the migration badge if applicable
910- migration_badge = f"""
911- <div style="
912- background-color: var(--yellow-bg);
913- color: var(--yellow-text);
914- padding: 6px 16px;
915- border-radius: 6px;
916- ">
917- <strong>{ migrations_count } dataset migration(s)</strong>
918- </div>
919- """ if migrations_count > 0 else ""
920-
921- # Build the status badge if applicable
922- status_badge = f"""
923- <div style="
924- background-color: var(--{ 'green' if status == "succeeded" else 'red' } -bg);
925- color: var(--{ 'green' if status == "succeeded" else 'red' } -text);
926- padding: 6px 16px;
927- border-radius: 6px;
928- ">
929- <strong>{ status } </strong>
930- </div>
931- """
932-
933- # Build the whole html
917+ # Build the whole html using CSS classes
934918 html = f"""
935- <div style="
936- padding-top: 16px;
937- padding-bottom: 10px;
938- ">
919+ <div class="pipeline-execution-container">
939920 <!-- Main 3-column flex container -->
940- <div style="
941- display: flex;
942- flex-direction: row;
943- justify-content: space-between;
944- align-items: center;
945- gap: 16px;
946- ">
921+ <div class="pipeline-execution-layout">
922+
947923 <!-- LEFT COLUMN: Run ID, Total time -->
948- <div style="
949- display: flex;
950- flex-direction: column;
951- ">
924+ <div class="pipeline-execution-info">
952925 { general_info }
953926 </div>
954927
955928 <!-- CENTER COLUMN: Timeline bar and legend -->
956- <div style="
957- display: flex;
958- flex-direction: column;
959- justify-content: space-between;
960- flex: 0 0 40%;
961- ">
962- { segments_bar }
963- { segment_labels }
929+ <div class="pipeline-execution-timeline">
930+ <div class="pipeline-execution-timeline-bar">
931+ { '' .join (segments )}
932+ </div>
933+ <div class="pipeline-execution-labels">
934+ { '' .join (labels )}
935+ </div>
964936 </div>
965937
966938 <!-- RIGHT COLUMN: Status badges -->
967- <div style="
968- display: flex;
969- flex-direction: row;
970- justify-content: space-between;
971- gap: 16px;
972- ">
973- { migration_badge }
974- { status_badge }
939+ <div class="pipeline-execution-badges">
940+ { _build_migration_badge (migrations_count )}
941+ { _build_status_badge (status )}
975942 </div>
976943 </div>
977944 </div>
@@ -1038,7 +1005,10 @@ def build_pipeline_execution_visualization(trace: PipelineTrace) -> Optional[mo.
10381005#
10391006
10401007
1041- PENDING_LOAD_STATUSES : Set [TLoadPackageStatus ] = {"extracted" , "normalized" }
1008+ PENDING_LOAD_STATUSES : Dict [TLoadPackageStatus , str ] = {
1009+ "extracted" : "pending to normalize" ,
1010+ "normalized" : "pending to load" ,
1011+ }
10421012
10431013LOAD_PACKAGE_STATUS_COLORS : Dict [TLoadPackageStatus , str ] = {
10441014 "new" : "grey" ,
@@ -1048,17 +1018,6 @@ def build_pipeline_execution_visualization(trace: PipelineTrace) -> Optional[mo.
10481018 "aborted" : "red" ,
10491019}
10501020
1051- LOAD_PACKAGE_STATUS_BADGE_HTML = (
1052- '<div style="'
1053- " background-color: var(--{k}-bg);"
1054- " color: var(--{k}-text);"
1055- " padding: 6px 16px;"
1056- " display: inline-block;"
1057- " border-radius: 6px;"
1058- '">'
1059- "<strong>{t}</strong></div>"
1060- )
1061-
10621021
10631022def _collect_load_packages_from_trace (
10641023 trace : PipelineTrace ,
@@ -1086,14 +1045,20 @@ def load_package_status_labels(trace: PipelineTrace) -> mo.ui.table:
10861045
10871046 for package in packages :
10881047 is_partial = PackageStorage .is_package_partially_loaded (package )
1089-
10901048 badge_color_key = "red" if is_partial else LOAD_PACKAGE_STATUS_COLORS .get (package .state )
1091- badge_text = (
1092- f"partially { package .state } "
1093- if is_partial
1094- else "pending" if package .state in PENDING_LOAD_STATUSES else package .state
1049+ if is_partial :
1050+ badge_text = f"partially { package .state } "
1051+ elif package .state in PENDING_LOAD_STATUSES :
1052+ badge_text = PENDING_LOAD_STATUSES .get (package .state )
1053+ elif package .state == "new" :
1054+ badge_text = "discarded"
1055+ else :
1056+ badge_text = package .state
1057+
1058+ status_html = (
1059+ '<div class="status-badge'
1060+ f' status-badge-{ badge_color_key } "><strong>{ badge_text } </strong></div>'
10951061 )
1096- status_html = LOAD_PACKAGE_STATUS_BADGE_HTML .format (k = badge_color_key , t = badge_text )
10971062 result .append (
10981063 {
10991064 "load_id" : package .load_id ,
0 commit comments