Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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: 2 additions & 0 deletions src/sentry/seer/endpoints/seer_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
get_issue_details,
get_replay_metadata,
get_repository_definition,
get_trace_item_attributes,
rpc_get_profile_flamegraph,
rpc_get_trace_waterfall,
)
Expand Down Expand Up @@ -1205,6 +1206,7 @@ def check_repository_integrations_status(*, repository_integrations: list[dict[s
"execute_trace_query_table": execute_trace_query_table,
"execute_table_query": execute_table_query,
"execute_timeseries_query": execute_timeseries_query,
"get_trace_item_attributes": get_trace_item_attributes,
"get_repository_definition": get_repository_definition,
"call_custom_tool": call_custom_tool,
#
Expand Down
57 changes: 57 additions & 0 deletions src/sentry/seer/explorer/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -913,3 +913,60 @@ def get_replay_metadata(
filter(lambda x: x[0] == int(result["project_id"]), p_ids_and_slugs)
)[1]
return result


def get_trace_item_attributes(
*,
org_id: int,
project_id: int,
trace_id: str,
item_id: str,
item_type: str,
) -> dict[str, Any]:
"""
Fetch all attributes for a given trace item (span, metric, log, etc.).

This is a generic version that supports all trace item types.

Args:
org_id: Organization ID
project_id: Project ID
trace_id: Trace ID
item_id: The item ID (span_id, metric_id, log_id, etc.)
item_type: The trace item type as a string ("spans", "tracemetrics", "logs", etc.)

Returns:
Dict with "attributes" key containing all attributes for the item
"""
try:
organization = Organization.objects.get(id=org_id)
except Organization.DoesNotExist:
logger.warning(
"get_trace_item_attributes: Organization not found",
extra={"org_id": org_id},
)
return {"attributes": []}

try:
project = Project.objects.get(id=project_id, organization=organization)
except Project.DoesNotExist:
logger.warning(
"get_trace_item_attributes: Project not found",
extra={"org_id": org_id, "project_id": project_id},
)
return {"attributes": []}

params = {
"item_type": item_type,
"referrer": Referrer.SEER_RPC.value,
"trace_id": trace_id,
}

resp = client.get(
auth=ApiKey(organization_id=organization.id, scope_list=["org:read", "project:read"]),
user=None,
path=f"/projects/{organization.slug}/{project.slug}/trace-items/{item_id}/",
params=params,
)

return resp.data
35 changes: 35 additions & 0 deletions tests/sentry/seer/endpoints/test_seer_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
get_organization_seer_consent_by_org_name,
get_sentry_organization_ids,
)
from sentry.seer.explorer.tools import get_trace_item_attributes
from sentry.testutils.cases import APITestCase
from sentry.testutils.silo import assume_test_silo_mode_of

Expand Down Expand Up @@ -378,6 +379,40 @@ def test_get_attributes_for_span(self) -> None:
assert attribute["name"] in {"span.description", "tags[span.description,string]"}
mock_rpc.assert_called_once()

def test_get_trace_item_attributes_metric(self) -> None:
"""Test get_trace_item_attributes with metric item_type"""
project = self.create_project(organization=self.organization)

mock_response_data = {
"itemId": "b582741a4a35039b",
"timestamp": "2025-11-16T19:14:12Z",
"attributes": [
{"name": "metric.name", "type": "str", "value": "http.request.duration"},
{"name": "value", "type": "float", "value": 123.45},
],
}

with patch("sentry.seer.explorer.tools.client.get") as mock_get:
mock_get.return_value.data = mock_response_data
result = get_trace_item_attributes(
org_id=self.organization.id,
project_id=project.id,
trace_id="23eef78c77a94766ac941cce6510c057",
item_id="b582741a4a35039b",
item_type="tracemetrics",
)

assert len(result["attributes"]) == 2
# Check that we have both types (order may vary)
types = {attr["type"] for attr in result["attributes"]}
assert types == {"str", "float"}
mock_get.assert_called_once()

# Verify the correct parameters were passed
call_kwargs = mock_get.call_args[1]
assert call_kwargs["params"]["item_type"] == "tracemetrics"
assert call_kwargs["params"]["trace_id"] == "23eef78c77a94766ac941cce6510c057"

@responses.activate
@override_settings(SEER_GHE_ENCRYPT_KEY=TEST_FERNET_KEY)
@patch("sentry.integrations.github_enterprise.client.get_jwt", return_value="jwt_token_1")
Expand Down
Loading