diff --git a/pyproject.toml b/pyproject.toml index 3da530fc3..fd419ec9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/src/uipath/tracing/_utils.py b/src/uipath/tracing/_utils.py index 1675d4f01..b9e4f1e34 100644 --- a/src/uipath/tracing/_utils.py +++ b/src/uipath/tracing/_utils.py @@ -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. @@ -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, } @@ -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 @@ -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 diff --git a/tests/tracing/test_span_utils.py b/tests/tracing/test_span_utils.py index 4a4a58491..0da577bca 100644 --- a/tests/tracing/test_span_utils.py +++ b/tests/tracing/test_span_utils.py @@ -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 diff --git a/uv.lock b/uv.lock index 81f311af3..45693a9fb 100644 --- a/uv.lock +++ b/uv.lock @@ -2486,7 +2486,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.4.20" +version = "2.4.21" source = { editable = "." } dependencies = [ { name = "applicationinsights" },