Skip to content

Commit bd243cd

Browse files
authored
Merge pull request #537 from UiPath/fix/terminal_style_improve
fix: add input validations in terminal
2 parents 2dd2a69 + d215694 commit bd243cd

8 files changed

Lines changed: 112 additions & 40 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.32"
3+
version = "2.1.33"
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: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,9 @@ async def action_resume(self) -> None:
123123
input = json.loads(input_data)
124124
except json.JSONDecodeError:
125125
return
126-
details_panel.current_run.input_data = input
126+
details_panel.current_run.resume_data = input
127127
asyncio.create_task(self._execute_runtime(details_panel.current_run))
128+
details_panel.switch_tab("run-tab")
128129

129130
async def action_execute_run(self) -> None:
130131
"""Execute a new run with UiPath runtime."""
@@ -161,7 +162,6 @@ async def _execute_runtime(self, run: ExecutionRun):
161162
try:
162163
context: UiPathRuntimeContext = self.runtime_factory.new_context(
163164
entrypoint=run.entrypoint,
164-
input_json=run.input_data,
165165
trace_id=str(uuid4()),
166166
execution_id=run.id,
167167
logs_min_level=env.get("LOG_LEVEL", "INFO"),
@@ -172,8 +172,10 @@ async def _execute_runtime(self, run: ExecutionRun):
172172

173173
if run.status == "suspended":
174174
context.resume = True
175+
context.input_json = run.resume_data
175176
self._add_info_log(run, f"Resuming execution: {run.entrypoint}")
176177
else:
178+
context.input_json = run.input_data
177179
self._add_info_log(run, f"Starting execution: {run.entrypoint}")
178180

179181
run.status = "running"

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

Lines changed: 73 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import json
21
from typing import Dict, List, Optional
32

43
from textual.app import ComposeResult
@@ -29,14 +28,7 @@ def show_span_details(self, trace_msg: TraceMessage):
2928
details_log = self.query_one("#span-details", RichLog)
3029
details_log.clear()
3130

32-
# Format span details
3331
details_log.write(f"[bold cyan]Span: {trace_msg.span_name}[/bold cyan]")
34-
details_log.write(f"[dim]Trace ID: {trace_msg.trace_id}[/dim]")
35-
details_log.write(f"[dim]Span ID: {trace_msg.span_id}[/dim]")
36-
details_log.write(f"[dim]Run ID: {trace_msg.run_id}[/dim]")
37-
38-
if trace_msg.parent_span_id:
39-
details_log.write(f"[dim]Parent Span: {trace_msg.parent_span_id}[/dim]")
4032

4133
details_log.write("") # Empty line
4234

@@ -68,6 +60,16 @@ def show_span_details(self, trace_msg: TraceMessage):
6860
for key, value in trace_msg.attributes.items():
6961
details_log.write(f" {key}: {value}")
7062

63+
details_log.write("") # Empty line
64+
65+
# Format span details
66+
details_log.write(f"[dim]Trace ID: {trace_msg.trace_id}[/dim]")
67+
details_log.write(f"[dim]Span ID: {trace_msg.span_id}[/dim]")
68+
details_log.write(f"[dim]Run ID: {trace_msg.run_id}[/dim]")
69+
70+
if trace_msg.parent_span_id:
71+
details_log.write(f"[dim]Parent Span: {trace_msg.parent_span_id}[/dim]")
72+
7173

7274
class RunDetailsPanel(Container):
7375
"""Panel showing traces and logs for selected run with tabbed interface."""
@@ -144,10 +146,65 @@ def show_run(self, run: ExecutionRun):
144146
# Clear and rebuild traces tree using TraceMessage objects
145147
self._rebuild_spans_tree()
146148

149+
def switch_tab(self, tab_id: str) -> None:
150+
"""Switch to a specific tab by id (e.g. 'run-tab', 'traces-tab')."""
151+
tabbed = self.query_one(TabbedContent)
152+
tabbed.active = tab_id
153+
147154
def _update_resume_tab(self, run: ExecutionRun) -> None:
148155
resume_panel = self.query_one("#resume-panel", ResumePanel)
149156
resume_panel.display = run.status == "suspended"
150157

158+
def _flatten_values(self, value: object, prefix: str = "") -> list[str]:
159+
"""Flatten nested dict/list structures into dot-notation paths."""
160+
lines: list[str] = []
161+
162+
if value is None:
163+
lines.append(f"{prefix}: [dim]—[/dim]" if prefix else "[dim]—[/dim]")
164+
165+
elif isinstance(value, dict):
166+
if not value:
167+
lines.append(f"{prefix}: {{}}" if prefix else "{}")
168+
else:
169+
for k, v in value.items():
170+
new_prefix = f"{prefix}.{k}" if prefix else k
171+
lines.extend(self._flatten_values(v, new_prefix))
172+
173+
elif isinstance(value, list):
174+
if not value:
175+
lines.append(f"{prefix}: []" if prefix else "[]")
176+
else:
177+
for i, item in enumerate(value):
178+
new_prefix = f"{prefix}[{i}]"
179+
lines.extend(self._flatten_values(item, new_prefix))
180+
181+
elif isinstance(value, str):
182+
if prefix:
183+
for line in value.splitlines():
184+
lines.append(f"{prefix}: {line}")
185+
else:
186+
lines.extend(value.splitlines())
187+
188+
else:
189+
if prefix:
190+
lines.append(f"{prefix}: {value}")
191+
else:
192+
lines.append(str(value))
193+
194+
return lines
195+
196+
def _write_block(
197+
self, log: RichLog, title: str, data: object, style: str = "white"
198+
) -> None:
199+
"""Pretty-print a block with flattened dot-notation paths."""
200+
log.write(f"[bold {style}]{title.upper()}:[/bold {style}]")
201+
log.write("[dim]" + "=" * 50 + "[/dim]")
202+
203+
for line in self._flatten_values(data):
204+
log.write(line)
205+
206+
log.write("")
207+
151208
def _show_run_details(self, run: ExecutionRun):
152209
"""Display detailed information about the run in the Details tab."""
153210
self._update_resume_tab(run)
@@ -202,35 +259,16 @@ def _show_run_details(self, run: ExecutionRun):
202259

203260
run_details_log.write("")
204261

205-
# Input section
206-
if hasattr(run, "input_data") and run.input_data is not None:
207-
run_details_log.write("[bold green]INPUT:[/bold green]")
208-
run_details_log.write("[dim]" + "=" * 50 + "[/dim]")
209-
210-
# Handle different input types
211-
if isinstance(run.input_data, str):
212-
run_details_log.write(run.input_data)
213-
elif isinstance(run.input_data, dict):
214-
run_details_log.write(json.dumps(run.input_data, indent=2))
215-
else:
216-
run_details_log.write(str(run.input_data))
217-
218-
run_details_log.write("")
262+
if hasattr(run, "input_data"):
263+
self._write_block(run_details_log, "Input", run.input_data, style="green")
219264

220-
# Output section
221-
if hasattr(run, "output_data") and run.output_data is not None:
222-
run_details_log.write("[bold magenta]OUTPUT:[/bold magenta]")
223-
run_details_log.write("[dim]" + "=" * 50 + "[/dim]")
224-
225-
# Handle different output types
226-
if isinstance(run.output_data, str):
227-
run_details_log.write(run.output_data)
228-
elif isinstance(run.output_data, dict):
229-
run_details_log.write(json.dumps(run.output_data, indent=2))
230-
else:
231-
run_details_log.write(str(run.output_data))
265+
if hasattr(run, "resume_data") and run.resume_data:
266+
self._write_block(run_details_log, "Resume", run.resume_data, style="green")
232267

233-
run_details_log.write("")
268+
if hasattr(run, "output_data"):
269+
self._write_block(
270+
run_details_log, "Output", run.output_data, style="magenta"
271+
)
234272

235273
# Error section (if applicable)
236274
if hasattr(run, "error") and run.error:
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import json
2+
3+
from textual.widgets import TextArea
4+
5+
6+
class JsonInput(TextArea):
7+
"""TextArea that validates JSON on change."""
8+
9+
def validate_json(self) -> bool:
10+
text = self.text.strip()
11+
if not text:
12+
self.remove_class("invalid")
13+
return True
14+
try:
15+
json.loads(text)
16+
self.remove_class("invalid")
17+
return True
18+
except json.JSONDecodeError:
19+
self.add_class("invalid")
20+
return False
21+
22+
def on_text_area_changed(self, event: TextArea.Changed) -> None:
23+
self.validate_json()

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from textual.reactive import reactive
88
from textual.widgets import Button, Select, TabbedContent, TabPane, TextArea
99

10+
from ._json_input import JsonInput
11+
1012

1113
def mock_json_from_schema(schema: Dict[str, Any]) -> Dict[str, Any]:
1214
props = schema.get("properties", {})
@@ -74,7 +76,7 @@ def compose(self) -> ComposeResult:
7476
allow_blank=False,
7577
)
7678

77-
yield TextArea(
79+
yield JsonInput(
7880
text=self.initial_input,
7981
language="json",
8082
id="json-input",

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from textual.containers import Container, Horizontal, Vertical
55
from textual.widgets import Button, TextArea
66

7+
from ._json_input import JsonInput
8+
79

810
class ResumePanel(Container):
911
"""Panel for resuming a suspended run."""
@@ -13,7 +15,7 @@ def __init__(self, **kwargs):
1315

1416
def compose(self) -> ComposeResult:
1517
with Vertical():
16-
yield TextArea(
18+
yield JsonInput(
1719
text=json.dumps({"value": ""}, indent=2),
1820
language="json",
1921
id="resume-json-input",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def __init__(self, entrypoint: str, input_data: Dict[str, Any]):
1616
self.id = str(uuid4())[:8]
1717
self.entrypoint = entrypoint
1818
self.input_data = input_data
19+
self.resume_data: Optional[Dict[str, Any]] = None
1920
self.output_data: Optional[Dict[str, Any]] = None
2021
self.start_time = datetime.now()
2122
self.end_time: Optional[datetime] = None

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,7 @@ SpanDetailsDisplay {
218218
text-style: bold;
219219
border: none;
220220
}
221+
222+
TextArea.invalid {
223+
border: tall red;
224+
}

0 commit comments

Comments
 (0)