Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath"
version = "2.4.20"
version = "2.4.21"
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
12 changes: 12 additions & 0 deletions src/uipath/tracing/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ class UiPathSpan:

job_key: Optional[str] = field(default_factory=lambda: env.get("UIPATH_JOB_KEY"))

# Top-level fields for internal tracing schema
execution_type: Optional[int] = None
agent_version: Optional[str] = None

def to_dict(self, serialize_attributes: bool = True) -> Dict[str, Any]:
"""Convert the Span to a dictionary suitable for JSON serialization.

Expand Down Expand Up @@ -137,6 +141,8 @@ def to_dict(self, serialize_attributes: bool = True) -> Dict[str, Any]:
"ProcessKey": self.process_key,
"JobKey": self.job_key,
"ReferenceId": self.reference_id,
"ExecutionType": self.execution_type,
"AgentVersion": self.agent_version,
}


Expand Down Expand Up @@ -285,6 +291,10 @@ def otel_span_to_uipath_span(
span_type_value = attributes_dict.get("span_type", "OpenTelemetry")
span_type = str(span_type_value)

# Top-level fields for internal tracing schema
execution_type = attributes_dict.get("executionType")
agent_version = attributes_dict.get("agentVersion")

# Create UiPathSpan from OpenTelemetry span
start_time = datetime.fromtimestamp(
(otel_span.start_time or 0) / 1e9
Expand All @@ -310,6 +320,8 @@ def otel_span_to_uipath_span(
end_time=end_time_str,
status=status,
span_type=span_type,
execution_type=execution_type,
agent_version=agent_version,
)

@staticmethod
Expand Down
108 changes: 108 additions & 0 deletions tests/tracing/test_span_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,111 @@ def test_otel_span_to_uipath_span_with_env_trace_id(self):

# Verify the trace ID is taken from environment
assert str(uipath_span.trace_id) == "00000000-0000-4000-8000-000000000000"

@patch.dict(os.environ, {"UIPATH_ORGANIZATION_ID": "test-org"})
def test_uipath_span_includes_execution_type(self):
"""Test that executionType from attributes becomes top-level ExecutionType."""
mock_span = Mock(spec=OTelSpan)

trace_id = 0x123456789ABCDEF0123456789ABCDEF0
span_id = 0x0123456789ABCDEF
mock_context = SpanContext(trace_id=trace_id, span_id=span_id, is_remote=False)
mock_span.get_span_context.return_value = mock_context

mock_span.name = "test-span"
mock_span.parent = None
mock_span.status.status_code = StatusCode.OK
mock_span.attributes = {"executionType": 0}
mock_span.events = []
mock_span.links = []

current_time_ns = int(datetime.now().timestamp() * 1e9)
mock_span.start_time = current_time_ns
mock_span.end_time = current_time_ns + 1000000

uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span)
span_dict = uipath_span.to_dict()

assert span_dict["ExecutionType"] == 0
assert uipath_span.execution_type == 0

@patch.dict(os.environ, {"UIPATH_ORGANIZATION_ID": "test-org"})
def test_uipath_span_includes_agent_version(self):
"""Test that agentVersion from attributes becomes top-level AgentVersion."""
mock_span = Mock(spec=OTelSpan)

trace_id = 0x123456789ABCDEF0123456789ABCDEF0
span_id = 0x0123456789ABCDEF
mock_context = SpanContext(trace_id=trace_id, span_id=span_id, is_remote=False)
mock_span.get_span_context.return_value = mock_context

mock_span.name = "test-span"
mock_span.parent = None
mock_span.status.status_code = StatusCode.OK
mock_span.attributes = {"agentVersion": "2.0.0"}
mock_span.events = []
mock_span.links = []

current_time_ns = int(datetime.now().timestamp() * 1e9)
mock_span.start_time = current_time_ns
mock_span.end_time = current_time_ns + 1000000

uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span)
span_dict = uipath_span.to_dict()

assert span_dict["AgentVersion"] == "2.0.0"
assert uipath_span.agent_version == "2.0.0"

@patch.dict(os.environ, {"UIPATH_ORGANIZATION_ID": "test-org"})
def test_uipath_span_execution_type_and_agent_version_both(self):
"""Test that both executionType and agentVersion are extracted together."""
mock_span = Mock(spec=OTelSpan)

trace_id = 0x123456789ABCDEF0123456789ABCDEF0
span_id = 0x0123456789ABCDEF
mock_context = SpanContext(trace_id=trace_id, span_id=span_id, is_remote=False)
mock_span.get_span_context.return_value = mock_context

mock_span.name = "Agent run - Agent"
mock_span.parent = None
mock_span.status.status_code = StatusCode.OK
mock_span.attributes = {"executionType": 1, "agentVersion": "1.0.0"}
mock_span.events = []
mock_span.links = []

current_time_ns = int(datetime.now().timestamp() * 1e9)
mock_span.start_time = current_time_ns
mock_span.end_time = current_time_ns + 1000000

uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span)
span_dict = uipath_span.to_dict()

assert span_dict["ExecutionType"] == 1
assert span_dict["AgentVersion"] == "1.0.0"

@patch.dict(os.environ, {"UIPATH_ORGANIZATION_ID": "test-org"})
def test_uipath_span_missing_execution_type_and_agent_version(self):
"""Test that missing executionType and agentVersion default to None."""
mock_span = Mock(spec=OTelSpan)

trace_id = 0x123456789ABCDEF0123456789ABCDEF0
span_id = 0x0123456789ABCDEF
mock_context = SpanContext(trace_id=trace_id, span_id=span_id, is_remote=False)
mock_span.get_span_context.return_value = mock_context

mock_span.name = "test-span"
mock_span.parent = None
mock_span.status.status_code = StatusCode.OK
mock_span.attributes = {"someOtherAttr": "value"}
mock_span.events = []
mock_span.links = []

current_time_ns = int(datetime.now().timestamp() * 1e9)
mock_span.start_time = current_time_ns
mock_span.end_time = current_time_ns + 1000000

uipath_span = _SpanUtils.otel_span_to_uipath_span(mock_span)
span_dict = uipath_span.to_dict()

assert span_dict["ExecutionType"] is None
assert span_dict["AgentVersion"] is None
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.