Browse Source

fix(spans): Don't treat span ids as uuids (#69705)

- In the span dataset `id` refers to the span_id, a 16 character hex
string, but because of hte validation in the field filtering inherits
from discover where id is a 32 character uuid validation would fail
- This updates the validation so it can take the dataset into account
William Mak 10 months ago
parent
commit
af466e674e

+ 2 - 1
src/sentry/search/events/builder/discover.py

@@ -91,6 +91,7 @@ class BaseQueryBuilder:
     requires_organization_condition: bool = False
     organization_column: str = "organization.id"
     free_text_key = "message"
+    uuid_fields = {"id", "trace", "profile.id", "replay.id"}
     function_alias_prefix: str | None = None
     spans_metrics_builder = False
     entity: Entity | None = None
@@ -1589,7 +1590,7 @@ class QueryBuilder(BaseQueryBuilder):
                 raise InvalidSearchQuery(INVALID_SPAN_ID.format(name))
 
         # Validate event ids, trace ids, and profile ids are uuids
-        if name in {"id", "trace", "profile.id", "replay.id"}:
+        if name in self.uuid_fields:
             if search_filter.value.is_wildcard():
                 raise InvalidSearchQuery(WILDCARD_NOT_ALLOWED.format(name))
             elif not search_filter.value.is_event_id():

+ 1 - 0
src/sentry/search/events/builder/spans_indexed.py

@@ -7,6 +7,7 @@ from sentry.search.events.types import SelectType
 class SpansIndexedQueryBuilder(QueryBuilder):
     requires_organization_condition = False
     free_text_key = "span.description"
+    uuid_fields = {"transaction.id", "replay.id", "profile.id", "trace"}
 
     def get_field_type(self, field: str) -> str | None:
         if field in self.meta_resolver_map:

+ 37 - 0
tests/snuba/api/endpoints/test_organization_events_span_indexed.py

@@ -232,6 +232,43 @@ class OrganizationEventsSpanIndexedEndpointTest(OrganizationEventsEndpointTestBa
         assert data[0]["origin.transaction"] == "/pageloads/"
         assert meta["dataset"] == "spansIndexed"
 
+    def test_id_filtering(self):
+        span = self.create_span({"description": "foo"}, start_ts=self.ten_mins_ago)
+        self.store_span(span)
+        response = self.do_request(
+            {
+                "field": ["description", "count()"],
+                "query": f"id:{span['span_id']}",
+                "orderby": "description",
+                "project": self.project.id,
+                "dataset": "spansIndexed",
+            }
+        )
+
+        assert response.status_code == 200, response.content
+        data = response.data["data"]
+        meta = response.data["meta"]
+        assert len(data) == 1
+        assert data[0]["description"] == "foo"
+        assert meta["dataset"] == "spansIndexed"
+
+        response = self.do_request(
+            {
+                "field": ["description", "count()"],
+                "query": f"transaction.id:{span['event_id']}",
+                "orderby": "description",
+                "project": self.project.id,
+                "dataset": "spansIndexed",
+            }
+        )
+
+        assert response.status_code == 200, response.content
+        data = response.data["data"]
+        meta = response.data["meta"]
+        assert len(data) == 1
+        assert data[0]["description"] == "foo"
+        assert meta["dataset"] == "spansIndexed"
+
     def test_span_op_casing(self):
         self.store_spans(
             [