1010 List ,
1111 Mapping ,
1212 Optional ,
13- Set ,
1413 Tuple ,
1514 Union ,
1615 cast ,
2827import marimo as mo
2928import pyarrow
3029import traceback
30+ import datetime # noqa: I251
3131
3232from dlt .common .configuration import resolve_configuration
3333from dlt .common .configuration .specs import known_sections
4545from dlt .common .configuration .exceptions import ConfigFieldMissingException
4646from dlt .common .typing import DictStrAny , TypedDict
4747from dlt .common .utils import map_nested_keys_in_place
48+ from dlt .common .pipeline import get_dlt_pipelines_dir
4849
4950from dlt ._workspace .helpers .dashboard import ui_elements as ui
5051from dlt ._workspace .helpers .dashboard .config import DashboardConfiguration
5152from dlt .destinations .exceptions import DatabaseUndefinedRelation , DestinationUndefinedEntity
5253from dlt .pipeline .exceptions import PipelineConfigMissing
5354from dlt .pipeline .exceptions import CannotRestorePipelineException
5455from dlt .pipeline .trace import PipelineTrace , PipelineStepTrace
56+ from dlt ._workspace .run_context import DEFAULT_WORKSPACE_WORKING_FOLDER
57+ from dlt ._workspace ._workspace_context import WorkspaceRunContext
5558
5659PICKLE_TRACE_FILE = "trace.pickle"
5760
@@ -75,6 +78,56 @@ def _exception_to_string(exception: Exception) -> str:
7578 return str (exception )
7679
7780
81+ def sync_from_runtime () -> None :
82+ """Sync the pipeline states and traces from the runtime backup, recursively."""
83+ from dlt .pipeline .runtime_artifacts import _get_runtime_artifacts_fs
84+ import fsspec
85+
86+ def sync_dir (fs : fsspec .filesystem , src_root : str , dst_root : str ) -> None :
87+ """Recursively sync src_root on fs into dst_root locally, always using fs.walk."""
88+ os .makedirs (dst_root , exist_ok = True )
89+
90+ for dirpath , _dirs , files in fs .walk (src_root ):
91+ # Compute local directory path
92+ relative = os .path .relpath (dirpath , src_root )
93+ local_dir = dst_root if relative == "." else os .path .join (dst_root , relative )
94+ os .makedirs (local_dir , exist_ok = True )
95+
96+ # Copy all files in this directory
97+ for filename in files :
98+ remote_file = fs .sep .join ([dirpath , filename ])
99+ local_file = os .path .join (local_dir , filename )
100+
101+ with fs .open (remote_file , "rb" ) as bf , open (local_file , "wb" ) as lf :
102+ lf .write (bf .read ())
103+
104+ # Try to preserve LastModified as mtime
105+ # needed for correct ordering of pipelines in pipeline list
106+ # TODO: this is a hack and probably should be done better...
107+ info = fs .info (remote_file )
108+ last_modified = info .get ("LastModified" ) or info .get ("last_modified" )
109+ if isinstance (last_modified , datetime .datetime ):
110+ ts = last_modified .timestamp ()
111+ os .utime (local_file , (ts , ts )) # (atime, mtime)
112+
113+ runtime_config = dlt .current .run_context ().runtime_config
114+
115+ if not (fs := _get_runtime_artifacts_fs (runtime_config )):
116+ return
117+
118+ context = dlt .current .run_context ()
119+ if not isinstance (context , WorkspaceRunContext ):
120+ return
121+
122+ src_base = runtime_config .workspace_pipeline_artifacts_sync_url # the artifacts folder on fs
123+ local_pipelines_dir = os .path .join (
124+ context .settings_dir , DEFAULT_WORKSPACE_WORKING_FOLDER
125+ ) # the local .var folder
126+
127+ # Just sync the whole base folder into the local pipelines dir
128+ sync_dir (fs , src_base , local_pipelines_dir )
129+
130+
78131def get_dashboard_config_sections (p : Optional [dlt .Pipeline ]) -> Tuple [str , ...]:
79132 """Find dashboard config section layout for a particular pipeline or for active
80133 run context type.
@@ -219,7 +272,7 @@ def pipeline_details(
219272 credentials = "Could not resolve credentials."
220273
221274 # find the pipeline in all_pipelines and get the timestamp
222- pipeline_timestamp = get_pipeline_last_run ( pipeline .pipeline_name , pipeline . pipelines_dir )
275+ trace = pipeline .last_trace
223276
224277 details_dict = {
225278 "pipeline_name" : pipeline .pipeline_name ,
@@ -228,7 +281,9 @@ def pipeline_details(
228281 if pipeline .destination
229282 else "No destination set"
230283 ),
231- "last executed" : _date_from_timestamp_with_ago (c , pipeline_timestamp ),
284+ "last executed" : (
285+ _date_from_timestamp_with_ago (c , trace .started_at ) if trace else "No trace found"
286+ ),
232287 "credentials" : credentials ,
233288 "dataset_name" : pipeline .dataset_name ,
234289 "working_dir" : pipeline .working_dir ,
@@ -667,7 +722,7 @@ def build_pipeline_link_list(
667722) -> str :
668723 """Build a list of links to the pipeline."""
669724 if not pipelines :
670- return "No local pipelines found."
725+ return "No pipelines found."
671726
672727 count = 0
673728 link_list : str = ""
@@ -750,12 +805,15 @@ def build_exception_section(p: dlt.Pipeline) -> List[Any]:
750805
751806
752807def _date_from_timestamp_with_ago (
753- config : DashboardConfiguration , timestamp : Union [int , float ]
808+ config : DashboardConfiguration , timestamp : Union [int , float , datetime . datetime ]
754809) -> str :
755810 """Return a date with ago section"""
756811 if not timestamp or timestamp == 0 :
757812 return "never"
758- p_ts = pendulum .from_timestamp (timestamp )
813+ if isinstance (timestamp , datetime .datetime ):
814+ p_ts = pendulum .instance (timestamp )
815+ else :
816+ p_ts = pendulum .from_timestamp (timestamp )
759817 time_formatted = p_ts .format (config .datetime_format )
760818 ago = p_ts .diff_for_humans ()
761819 return f"{ ago } ({ time_formatted } )"
0 commit comments