Skip to content
Draft
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
5 changes: 0 additions & 5 deletions src/sentry/api/endpoints/project_trace_item_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,6 @@ def convert_rpc_attribute_to_json(
if external_name in seen_sentry_conventions:
continue
seen_sentry_conventions.add(external_name)
else:
if external_name and is_sentry_convention_replacement_attribute(
external_name, trace_item_type
):
continue

if trace_item_type == SupportedTraceItemType.SPANS and internal_name.startswith("sentry."):
internal_name = internal_name.replace("sentry.", "", count=1)
Expand Down
17 changes: 17 additions & 0 deletions src/sentry/search/eap/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,17 @@ def add_start_end_conditions(
}


SENTRY_CONVENTIONS_REVERSE_REPLACEMENT_MAP: dict[SupportedTraceItemType, dict[str, set[str]]] = {}
for _item_type, _replacement_map in SENTRY_CONVENTIONS_REPLACEMENT_MAPPINGS.items():
_internal_mapping = PUBLIC_ALIAS_TO_INTERNAL_MAPPING.get(_item_type, {})
_reverse: dict[str, set[str]] = {}
for _deprecated_alias, _replacement in _replacement_map.items():
_resolved = _internal_mapping.get(_deprecated_alias)
_internal_name = _resolved.internal_name if _resolved else _deprecated_alias
_reverse.setdefault(_replacement, set()).add(_internal_name)
SENTRY_CONVENTIONS_REVERSE_REPLACEMENT_MAP[_item_type] = _reverse


INTERNAL_TO_SECONDARY_ALIASES: dict[SupportedTraceItemType, dict[str, set[str]]] = {
SupportedTraceItemType.SPANS: SPAN_INTERNAL_TO_SECONDARY_ALIASES_MAPPING,
SupportedTraceItemType.LOGS: LOGS_INTERNAL_TO_SECONDARY_ALIASES_MAPPING,
Expand Down Expand Up @@ -232,6 +243,12 @@ def is_sentry_convention_replacement_attribute(
return public_alias in SENTRY_CONVENTIONS_REPLACEMENT_ATTRIBUTES.get(item_type, {})


def get_deprecated_source_internal_names(
replacement: str, item_type: SupportedTraceItemType
) -> set[str]:
return SENTRY_CONVENTIONS_REVERSE_REPLACEMENT_MAP.get(item_type, {}).get(replacement, set())


def translate_to_sentry_conventions(public_alias: str, item_type: SupportedTraceItemType) -> str:
mapping = SENTRY_CONVENTIONS_REPLACEMENT_MAPPINGS.get(item_type, {})
return mapping.get(public_alias, public_alias)
56 changes: 56 additions & 0 deletions tests/sentry/api/endpoints/test_project_trace_item_details.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from sentry.api.endpoints.project_trace_item_details import convert_rpc_attribute_to_json
from sentry.search.eap.types import SupportedTraceItemType

Expand Down Expand Up @@ -72,3 +74,57 @@ def test_convert_rpc_attribute_to_json_exposes_array_with_array_flag() -> None:
"value": ["assistant output"],
}
]


class TestReplacementAttributeFiltering:
"""When use_sentry_conventions is off, replacement attributes should only be
hidden if a deprecated source attribute is also present in the response."""

@pytest.mark.parametrize(
"attr_name,attr_value",
[
("gen_ai.usage.input_tokens", {"valInt": "42"}),
("gen_ai.input.messages", {"valStr": '["hello"]'}),
("gen_ai.output.messages", {"valStr": '["world"]'}),
],
)
def test_replacement_attribute_shown_when_no_deprecated_source(
self, attr_name: str, attr_value: dict[str, str]
) -> None:
result = convert_rpc_attribute_to_json(
[{"name": attr_name, "value": attr_value}],
SupportedTraceItemType.SPANS,
use_sentry_conventions=False,
)

assert len(result) == 1
assert result[0]["name"] == attr_name

def test_replacement_attribute_shown_alongside_deprecated_source(self) -> None:
result = convert_rpc_attribute_to_json(
[
{"name": "gen_ai.usage.prompt_tokens", "value": {"valInt": "42"}},
{"name": "gen_ai.usage.input_tokens", "value": {"valInt": "42"}},
],
SupportedTraceItemType.SPANS,
use_sentry_conventions=False,
)

names = [r["name"] for r in result]
assert "gen_ai.usage.prompt_tokens" in names
assert "gen_ai.usage.input_tokens" in names

def test_replacement_array_shown_when_no_deprecated_source(self) -> None:
result = convert_rpc_attribute_to_json(
[
{
"name": "gen_ai.output.messages",
"value": {"valArray": {"values": [{"valStr": "output"}]}},
}
],
SupportedTraceItemType.SPANS,
use_sentry_conventions=False,
)

assert len(result) == 1
assert result[0]["name"] == "gen_ai.output.messages"
Loading