Skip to content
Closed
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
49 changes: 39 additions & 10 deletions src/sentry/api/endpoints/organization_spans_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from sentry.search.eap.resolver import SearchResolver
from sentry.search.eap.spans.definitions import SPAN_DEFINITIONS
from sentry.search.eap.types import SearchResolverConfig, SupportedTraceItemType
from sentry.search.eap.utils import can_expose_attribute, translate_internal_to_public_alias
from sentry.search.eap.utils import can_expose_attribute_to_api, translate_internal_to_public_alias
from sentry.search.events.types import SnubaParams
from sentry.snuba.referrer import Referrer
from sentry.tagstore.types import TagValue
Expand Down Expand Up @@ -125,7 +125,7 @@ def get(self, request: Request, organization: Organization) -> Response:
as_tag_key(attribute.name, serialized["type"])
for attribute in rpc_response.attributes
if attribute.name
and can_expose_attribute(
and can_expose_attribute_to_api(
attribute.name,
SupportedTraceItemType.SPANS,
include_internal=include_internal,
Expand Down Expand Up @@ -164,15 +164,25 @@ def get(self, request: Request, organization: Organization, key: str) -> Respons

max_span_tag_values = options.get("performance.spans-tags-values.max")

executor = EAPSpanFieldValuesAutocompletionExecutor(
organization=organization,
snuba_params=snuba_params,
key=key,
query=request.GET.get("query"),
max_span_tag_values=max_span_tag_values,
)

with handle_query_errors():
executor = EAPSpanFieldValuesAutocompletionExecutor(
organization=organization,
snuba_params=snuba_params,
key=key,
query=request.GET.get("query"),
max_span_tag_values=max_span_tag_values,
include_internal=is_active_superuser(request) or is_active_staff(request),
)

if not executor.can_expose_attribute_to_api():
return self.paginate(
request=request,
paginator=ChainPaginator([]),
on_results=lambda results: serialize(results, request.user),
default_per_page=max_span_tag_values,
max_per_page=max_span_tag_values,
)

tag_values = executor.execute()

tag_values.sort(key=lambda tag: tag.value or "")
Expand Down Expand Up @@ -245,8 +255,10 @@ def __init__(
key: str,
query: str | None,
max_span_tag_values: int,
include_internal: bool,
):
super().__init__(organization, snuba_params, key, query, max_span_tag_values)
self.include_internal = include_internal
self.resolver = SearchResolver(
params=snuba_params, config=SearchResolverConfig(), definitions=SPAN_DEFINITIONS
)
Expand All @@ -258,6 +270,23 @@ def resolve_attribute_key(
resolved, _ = self.resolver.resolve_attribute(key)
return resolved.search_type, resolved.proto_definition

def can_expose_attribute_to_api(self) -> bool:
is_public_defined_attribute = (
self.key in SPAN_DEFINITIONS.columns or self.key in SPAN_DEFINITIONS.contexts
)
return can_expose_attribute_to_api(
self.key,
SupportedTraceItemType.SPANS,
include_internal=self.include_internal,
) and (
is_public_defined_attribute
or can_expose_attribute_to_api(
self.attribute_key.name,
SupportedTraceItemType.SPANS,
include_internal=self.include_internal,
)
)

def execute(self) -> list[TagValue]:
if self.key in self.PROJECT_ID_KEYS:
return self.project_id_autocomplete_function()
Expand Down
125 changes: 98 additions & 27 deletions src/sentry/api/endpoints/organization_trace_item_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
SupportedTraceItemType,
)
from sentry.search.eap.utils import (
can_expose_attribute,
can_expose_attribute_to_api,
get_secondary_aliases,
is_sentry_convention_replacement_attribute,
translate_internal_to_public_alias,
Expand Down Expand Up @@ -321,12 +321,21 @@ def get(self, request: Request, organization: Organization) -> Response:
trace_item_type = SupportedTraceItemType(dataset)
referrer = resolve_attribute_referrer(trace_item_type)
column_definitions = get_column_definitions(trace_item_type)
include_internal = is_active_superuser(request) or is_active_staff(request)
resolver = SearchResolver(
params=snuba_params,
config=SearchResolverConfig(),
config=SearchResolverConfig(
api_attribute_visibility_item_type=trace_item_type.value,
api_attribute_visibility_include_internal=include_internal,
),
definitions=column_definitions,
)
query_filter, _, _ = resolver.resolve_query(query_string)
if resolver.has_hidden_api_attributes():
return self.paginate(
request=request,
paginator=ChainPaginator([]),
)
meta = resolver.resolve_meta(referrer=referrer.value)
meta.trace_item_type = constants.SUPPORTED_TRACE_ITEM_TYPE_MAP.get(
trace_item_type, ProtoTraceItemType.TRACE_ITEM_TYPE_SPAN
Expand All @@ -338,8 +347,6 @@ def get(self, request: Request, organization: Organization) -> Response:
snuba_params.start = adjusted_start_date
snuba_params.end = adjusted_end_date

include_internal = is_active_superuser(request) or is_active_staff(request)

def data_fn(offset: int, limit: int) -> list[TraceItemAttributeKey]:
futures = []
with ContextPropagatingThreadPoolExecutor(
Expand Down Expand Up @@ -404,6 +411,11 @@ def query_trace_attributes(
and substring_match in column.public_alias
and not column.secondary_alias
and not column.private
and can_expose_attribute_to_api(
column.public_alias,
trace_item_type,
include_internal=include_internal,
)
):
all_aliased_attributes.append(column)
for (
Expand All @@ -415,6 +427,11 @@ def query_trace_attributes(
and virtual_context.search_type is not None
and not virtual_context.secondary_alias
and constants.TYPE_MAP[virtual_context.search_type] == attr_type
and can_expose_attribute_to_api(
public_label,
trace_item_type,
include_internal=include_internal,
)
):
all_aliased_attributes.append(
ProxyResolvedAttribute(
Expand All @@ -433,6 +450,11 @@ def query_trace_attributes(
and virtual_context.search_type is not None
and not virtual_context.secondary_alias
and constants.TYPE_MAP[virtual_context.search_type] == attr_type
and can_expose_attribute_to_api(
public_label,
trace_item_type,
include_internal=include_internal,
)
):
all_aliased_attributes.append(
ProxyResolvedAttribute(
Expand Down Expand Up @@ -493,7 +515,7 @@ def serialize_trace_attributes_using_sentry_conventions(
) -> list[TraceItemAttributeKey]:
attribute_keys = {}
for attribute in rpc_response.attributes:
if attribute.name and can_expose_attribute(
if attribute.name and can_expose_attribute_to_api(
attribute.name,
trace_item_type,
include_internal=include_internal,
Expand Down Expand Up @@ -523,6 +545,14 @@ def serialize_trace_attributes_using_sentry_conventions(
if attr_key["name"] in attribute_keys:
del attribute_keys[attr_key["name"]]
for aliased_attr in aliased_attributes:
if not (
can_expose_attribute_to_api(
aliased_attr.public_alias,
trace_item_type,
include_internal=include_internal,
)
):
continue
attr_key = as_attribute_key(
aliased_attr.internal_name,
attribute_type,
Expand All @@ -547,7 +577,7 @@ def serialize_trace_attributes(
) -> list[TraceItemAttributeKey]:
attribute_keys = {}
for attribute in rpc_response.attributes:
if attribute.name and can_expose_attribute(
if attribute.name and can_expose_attribute_to_api(
attribute.name,
trace_item_type,
include_internal=include_internal,
Expand Down Expand Up @@ -579,18 +609,21 @@ def serialize_trace_attributes(
if attr_key["name"] in attribute_keys:
del attribute_keys[attr_key["name"]]
for aliased_attr in aliased_attributes:
if can_expose_attribute(
aliased_attr.public_alias,
trace_item_type,
include_internal=include_internal,
):
attr_key = as_attribute_key(
aliased_attr.internal_name,
attribute_type,
if not (
can_expose_attribute_to_api(
aliased_attr.public_alias,
trace_item_type,
is_proxy=isinstance(aliased_attr, ProxyResolvedAttribute),
include_internal=include_internal,
)
attribute_keys[attr_key["key"]] = attr_key
):
continue
attr_key = as_attribute_key(
aliased_attr.internal_name,
attribute_type,
trace_item_type,
is_proxy=isinstance(aliased_attr, ProxyResolvedAttribute),
)
attribute_keys[attr_key["key"]] = attr_key
attributes = list(attribute_keys.values())
sentry_sdk.set_context("api_response", {"attributes": attributes})
return attributes
Expand Down Expand Up @@ -628,19 +661,25 @@ def get(self, request: Request, organization: Organization, key: str) -> Respons
max_attribute_values = options.get("explore.trace-items.values.max")

definitions = get_column_definitions(SupportedTraceItemType(dataset))
include_internal = is_active_superuser(request) or is_active_staff(request)

def data_fn(offset: int, limit: int):
executor = TraceItemAttributeValuesAutocompletionExecutor(
organization=organization,
snuba_params=snuba_params,
key=key,
query=substring_match,
limit=limit,
offset=offset,
definitions=definitions,
)

with handle_query_errors():
executor = TraceItemAttributeValuesAutocompletionExecutor(
organization=organization,
snuba_params=snuba_params,
key=key,
query=substring_match,
limit=limit,
offset=offset,
definitions=definitions,
item_type=SupportedTraceItemType(dataset),
include_internal=include_internal,
)

if not executor.can_expose_attribute_to_api():
return []

tag_values = executor.execute()
tag_values.sort(key=lambda tag: tag.value or "")
return tag_values
Expand All @@ -664,10 +703,15 @@ def __init__(
limit: int,
offset: int,
definitions: ColumnDefinitions,
item_type: SupportedTraceItemType,
include_internal: bool,
):
super().__init__(organization, snuba_params, key, query, limit)
self.limit = limit
self.offset = offset
self.item_type = item_type
self.include_internal = include_internal
self.definitions = definitions
self.resolver = SearchResolver(
params=snuba_params, config=SearchResolverConfig(), definitions=definitions
)
Expand Down Expand Up @@ -698,6 +742,23 @@ def resolve_attribute_key(
context_definition,
)

def can_expose_attribute_to_api(self) -> bool:
is_public_defined_attribute = (
self.key in self.definitions.columns or self.key in self.definitions.contexts
)
return can_expose_attribute_to_api(
self.key,
self.item_type,
include_internal=self.include_internal,
) and (
is_public_defined_attribute
or can_expose_attribute_to_api(
self.attribute_key.name,
self.item_type,
include_internal=self.include_internal,
)
)

def execute(self) -> list[TagValue]:
func = self.autocomplete_function.get(self.key)

Expand Down Expand Up @@ -1033,6 +1094,7 @@ def post(self, request: Request, organization: Organization) -> Response:

item_type = SupportedTraceItemType(query_serializer.validated_data["item_type"])
attribute_names: list[str] = serializer.validated_data["attributes"]
include_internal = is_active_superuser(request) or is_active_staff(request)

try:
snuba_params = self.get_snuba_params(request, organization)
Expand All @@ -1045,7 +1107,10 @@ def post(self, request: Request, organization: Organization) -> Response:
return Response({"detail": f"Unsupported item type: {item_type.value}"}, status=400)
resolver = SearchResolver(
params=snuba_params,
config=SearchResolverConfig(),
config=SearchResolverConfig(
api_attribute_visibility_item_type=item_type.value,
api_attribute_visibility_include_internal=include_internal,
),
definitions=definitions,
)

Expand All @@ -1056,6 +1121,12 @@ def post(self, request: Request, organization: Organization) -> Response:
for attr_name in attribute_names:
try:
resolved, _context = resolver.resolve_attribute(attr_name)
if resolver.is_hidden_api_attribute(attr_name):
results[attr_name] = {
"valid": False,
"error": f"Unknown attribute: {attr_name}",
}
continue
if attr_name in definitions.contexts or attr_name in definitions.columns:
# Known column or virtual context — always valid
results[attr_name] = {
Expand Down
Loading
Loading