Skip to content

Commit 320f557

Browse files
committed
fix: add terminal resume tab
1 parent 4914c65 commit 320f557

6 files changed

Lines changed: 75 additions & 23 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath"
3-
version = "2.1.31"
3+
version = "2.1.32"
44
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.10"

src/uipath/_cli/_dev/_terminal/__init__.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
UiPathRuntimeContext,
1818
UiPathRuntimeError,
1919
UiPathRuntimeFactory,
20+
UiPathRuntimeStatus,
2021
)
2122
from ._components._details import RunDetailsPanel
2223
from ._components._history import RunHistoryPanel
2324
from ._components._new import NewRunPanel
25+
from ._components._resume import ResumePanel
2426
from ._models._execution import ExecutionRun
2527
from ._models._messages import LogMessage, TraceMessage
2628
from ._traces._exporter import RunContextExporter
@@ -86,6 +88,8 @@ async def on_button_pressed(self, event: Button.Pressed) -> None:
8688
await self.action_execute_run()
8789
elif event.button.id == "cancel-btn":
8890
await self.action_cancel()
91+
elif event.button.id == "resume-btn":
92+
await self.action_resume()
8993

9094
async def on_list_view_selected(self, event: ListView.Selected) -> None:
9195
"""Handle run selection from history."""
@@ -109,6 +113,19 @@ async def action_cancel(self) -> None:
109113
"""Cancel and return to new run view."""
110114
await self.action_new_run()
111115

116+
async def action_resume(self) -> None:
117+
"""Resume the suspended run."""
118+
details_panel = self.query_one("#details-panel", RunDetailsPanel)
119+
if details_panel and details_panel.current_run:
120+
input: Dict[str, Any] = {}
121+
input_data = self.query_one("#resume-panel", ResumePanel).get_input_values()
122+
try:
123+
input = json.loads(input_data)
124+
except json.JSONDecodeError:
125+
return
126+
details_panel.current_run.input_data = input
127+
asyncio.create_task(self._execute_runtime(details_panel.current_run))
128+
112129
async def action_execute_run(self) -> None:
113130
"""Execute a new run with UiPath runtime."""
114131
new_run_panel = self.query_one("#new-run-panel", NewRunPanel)
@@ -153,17 +170,27 @@ async def _execute_runtime(self, run: ExecutionRun):
153170
),
154171
)
155172

156-
self._add_info_log(run, f"Starting execution: {run.entrypoint}")
173+
if run.status == "suspended":
174+
context.resume = True
175+
self._add_info_log(run, f"Resuming execution: {run.entrypoint}")
176+
else:
177+
self._add_info_log(run, f"Starting execution: {run.entrypoint}")
178+
179+
run.status = "running"
180+
run.start_time = datetime.now()
157181

158182
result = await self.runtime_factory.execute_in_root_span(context)
159183

160184
if result is not None:
161-
run.output_data = result.output
185+
if result.status == UiPathRuntimeStatus.SUSPENDED.value:
186+
run.status = "suspended"
187+
else:
188+
run.output_data = result.output
189+
run.status = "completed"
162190
if run.output_data:
163191
self._add_info_log(run, f"Execution result: {run.output_data}")
164192

165193
self._add_info_log(run, "✅ Execution completed successfully")
166-
run.status = "completed"
167194
run.end_time = datetime.now()
168195

169196
except UiPathRuntimeError as e:

src/uipath/_cli/_dev/_terminal/_components/_details.py

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from .._models._execution import ExecutionRun
1111
from .._models._messages import LogMessage, TraceMessage
12+
from ._resume import ResumePanel
1213

1314

1415
class SpanDetailsDisplay(Container):
@@ -113,6 +114,9 @@ def compose(self) -> ComposeResult:
113114
classes="detail-log",
114115
)
115116

117+
with TabPane("Resume", id="resume-tab"):
118+
yield ResumePanel(id="resume-panel")
119+
116120
def watch_current_run(
117121
self, old_value: Optional[ExecutionRun], new_value: Optional[ExecutionRun]
118122
):
@@ -140,8 +144,14 @@ def show_run(self, run: ExecutionRun):
140144
# Clear and rebuild traces tree using TraceMessage objects
141145
self._rebuild_spans_tree()
142146

147+
def _update_resume_tab(self, run: ExecutionRun) -> None:
148+
resume_panel = self.query_one("#resume-panel", ResumePanel)
149+
resume_panel.display = run.status == "suspended"
150+
143151
def _show_run_details(self, run: ExecutionRun):
144152
"""Display detailed information about the run in the Details tab."""
153+
self._update_resume_tab(run)
154+
145155
run_details_log = self.query_one("#run-details-log", RichLog)
146156
run_details_log.clear()
147157

@@ -232,25 +242,6 @@ def _show_run_details(self, run: ExecutionRun):
232242
run_details_log.write(f"[red]\n{run.error.detail}[/red]")
233243
run_details_log.write("")
234244

235-
# Additional metadata
236-
run_details_log.write("[bold]METADATA:[/bold]")
237-
run_details_log.write("[dim]" + "=" * 50 + "[/dim]")
238-
239-
# Show available attributes
240-
for attr in ["id", "status", "start_time", "end_time", "duration_ms"]:
241-
if hasattr(run, attr):
242-
value = getattr(run, attr)
243-
if value is not None:
244-
run_details_log.write(f" {attr}: {value}")
245-
246-
# Show traces count
247-
traces_count = len(run.traces) if run.traces else 0
248-
run_details_log.write(f" traces_count: {traces_count}")
249-
250-
# Show logs count
251-
logs_count = len(run.logs) if run.logs else 0
252-
run_details_log.write(f" logs_count: {logs_count}")
253-
254245
def _rebuild_spans_tree(self):
255246
"""Rebuild the spans tree from current run's traces."""
256247
spans_tree = self.query_one("#spans-tree", Tree)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from textual.app import ComposeResult
2+
from textual.containers import Container, Horizontal, Vertical
3+
from textual.widgets import Button, TextArea
4+
5+
6+
class ResumePanel(Container):
7+
"""Panel for resuming a suspended run."""
8+
9+
def __init__(self, **kwargs):
10+
super().__init__(**kwargs)
11+
12+
def compose(self) -> ComposeResult:
13+
with Vertical():
14+
yield TextArea(
15+
text="{}",
16+
language="json",
17+
id="resume-json-input",
18+
classes="input-field json-input",
19+
)
20+
with Horizontal(classes="run-actions"):
21+
yield Button(
22+
"▶ Resume",
23+
id="resume-btn",
24+
variant="primary",
25+
classes="action-btn",
26+
)
27+
28+
def get_input_values(self) -> str:
29+
"""Return the JSON text to resume with."""
30+
json_input = self.query_one("#resume-json-input", TextArea)
31+
return json_input.text.strip()

src/uipath/_cli/_dev/_terminal/_models/_execution.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@ def duration(self) -> str:
3737
def display_name(self) -> Text:
3838
status_colors = {
3939
"running": "yellow",
40+
"suspended": "cyan",
4041
"completed": "green",
4142
"failed": "red",
4243
}
4344

4445
status_icon = {
4546
"running": "▶",
47+
"suspended": "⏸",
4648
"completed": "✔",
4749
"failed": "✖",
4850
}.get(self.status, "?")

src/uipath/_cli/_dev/_terminal/_styles/terminal.tcss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ Screen {
117117
}
118118

119119
Footer {
120+
margin-top:1;
120121
height: auto;
121122
dock: bottom;
122123
}

0 commit comments

Comments
 (0)