Skip to content

Commit e06d81b

Browse files
committed
Styles moved, improved ui
1 parent f587002 commit e06d81b

File tree

5 files changed

+170
-133
lines changed

5 files changed

+170
-133
lines changed

dlt/_workspace/helpers/dashboard/dlt_dashboard.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def home(
7272
else:
7373
_buttons: List[Any] = []
7474
_buttons.append(dlt_refresh_button)
75+
_pipeline_execution_exception: List[Any] = []
7576
_pipeline_execution_summary: mo.Html = None
7677
_last_load_packages_info: mo.Html = None
7778
if dlt_pipeline:
@@ -88,16 +89,15 @@ def home(
8889
on_click=lambda _: utils.open_local_folder(local_dir),
8990
)
9091
)
91-
if dlt_pipeline.last_trace:
92-
_pipeline_execution_summary = utils.build_pipeline_execution_visualization(
93-
dlt_pipeline.last_trace
94-
)
92+
if trace := dlt_pipeline.last_trace:
93+
_pipeline_execution_summary = utils.build_pipeline_execution_visualization(trace)
9594
_last_load_packages_info = mo.vstack(
9695
[
9796
mo.md(f"<small>{strings.view_load_packages_text}</small>"),
98-
utils.load_package_status_labels(dlt_pipeline.last_trace),
97+
utils.load_package_status_labels(trace),
9998
]
10099
)
100+
_pipeline_execution_exception = utils.build_exception_section(dlt_pipeline)
101101
_stack = [
102102
mo.vstack(
103103
[
@@ -114,10 +114,14 @@ def home(
114114
),
115115
]
116116
),
117-
_pipeline_execution_summary,
118-
_last_load_packages_info,
119117
mo.hstack(_buttons, justify="start"),
120118
]
119+
if _pipeline_execution_summary:
120+
_stack.append(_pipeline_execution_summary)
121+
if _last_load_packages_info:
122+
_stack.append(_last_load_packages_info)
123+
if _pipeline_execution_exception:
124+
_stack.extend(_pipeline_execution_exception)
121125
if not dlt_pipeline and dlt_pipeline_name:
122126
_stack.append(
123127
mo.callout(
@@ -152,8 +156,6 @@ def section_overview(
152156
)
153157

154158
if dlt_pipeline and dlt_section_overview_switch.value:
155-
if _exception_section := utils.build_exception_section(dlt_pipeline):
156-
_result.extend(_exception_section)
157159
_result += [
158160
mo.ui.table(
159161
utils.pipeline_details(dlt_config, dlt_pipeline, dlt_pipelines_dir),

dlt/_workspace/helpers/dashboard/dlt_dashboard_styles.css

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,79 @@
100100
marimo-callout-output .border {
101101
margin-top: 0.5rem;
102102
margin-bottom: 0.5rem;
103+
}
104+
105+
/* Pipeline execution visualization styles */
106+
.pipeline-execution-container {
107+
padding-top: 16px;
108+
padding-bottom: 10px;
109+
}
110+
111+
.pipeline-execution-layout {
112+
display: flex;
113+
flex-direction: row;
114+
justify-content: space-between;
115+
align-items: center;
116+
gap: 16px;
117+
}
118+
119+
.pipeline-execution-info {
120+
display: flex;
121+
flex-direction: column;
122+
}
123+
124+
.pipeline-execution-timeline {
125+
display: flex;
126+
flex-direction: column;
127+
justify-content: space-between;
128+
flex: 0 0 40%;
129+
}
130+
131+
.pipeline-execution-timeline-bar {
132+
display: flex;
133+
flex-direction: row;
134+
justify-content: center;
135+
height: 16px;
136+
}
137+
138+
/* Note: .pipeline-execution-timeline-segment uses inline styles for dynamic width, color, and border-radius */
139+
140+
.pipeline-execution-labels {
141+
display: flex;
142+
flex-direction: row;
143+
justify-content: space-between;
144+
}
145+
146+
.pipeline-execution-badges {
147+
display: flex;
148+
flex-direction: row;
149+
justify-content: space-between;
150+
gap: 16px;
151+
}
152+
153+
/* Status badge styles */
154+
.status-badge {
155+
padding: 6px 16px;
156+
border-radius: 6px;
157+
display: inline-block;
158+
}
159+
160+
.status-badge-yellow {
161+
background-color: var(--yellow-bg);
162+
color: var(--yellow-text);
163+
}
164+
165+
.status-badge-green {
166+
background-color: var(--green-bg);
167+
color: var(--green-text);
168+
}
169+
170+
.status-badge-red {
171+
background-color: var(--red-bg);
172+
color: var(--red-text);
173+
}
174+
175+
.status-badge-grey {
176+
background-color: var(--grey-bg);
177+
color: var(--grey-text);
103178
}

dlt/_workspace/helpers/dashboard/utils.py

Lines changed: 61 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
860880
def _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

10431013
LOAD_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

10631022
def _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

Comments
 (0)