Browse Source

fix(discovery): Fixes aliased search keys + issue shortname + project name queries (#43851)

Richard Ortenberg 2 years ago
parent
commit
42ed5e7818

+ 11 - 10
src/sentry/api/endpoints/organization_group_index.py

@@ -238,16 +238,6 @@ class OrganizationGroupIndexEndpoint(OrganizationEventsEndpointBase):
 
         environments = self.get_environments(request, organization)
 
-        serializer = functools.partial(
-            StreamGroupSerializerSnuba,
-            environment_ids=[env.id for env in environments],
-            stats_period=stats_period,
-            stats_period_start=stats_period_start,
-            stats_period_end=stats_period_end,
-            expand=expand,
-            collapse=collapse,
-        )
-
         projects = self.get_projects(request, organization)
         project_ids = [p.id for p in projects]
 
@@ -261,6 +251,17 @@ class OrganizationGroupIndexEndpoint(OrganizationEventsEndpointBase):
                 {"detail": "You do not have the multi project stream feature enabled"}, status=400
             )
 
+        serializer = functools.partial(
+            StreamGroupSerializerSnuba,
+            environment_ids=[env.id for env in environments],
+            stats_period=stats_period,
+            stats_period_start=stats_period_start,
+            stats_period_end=stats_period_end,
+            expand=expand,
+            collapse=collapse,
+            project_ids=project_ids,
+        )
+
         # we ignore date range for both short id and event ids
         query = request.GET.get("query", "").strip()
         if query:

+ 1 - 0
src/sentry/api/endpoints/organization_group_index_stats.py

@@ -115,6 +115,7 @@ class OrganizationGroupIndexStatsEndpoint(OrganizationEventsEndpointBase):
                 if "search_filters" in query_kwargs
                 else None,
                 organization_id=organization.id,
+                project_ids=project_ids,
             ),
         )
 

+ 27 - 17
src/sentry/api/serializers/models/group.py

@@ -65,7 +65,7 @@ from sentry.notifications.helpers import (
 from sentry.notifications.types import NotificationSettingTypes
 from sentry.reprocessing2 import get_progress
 from sentry.search.events.constants import RELEASE_STAGE_ALIAS
-from sentry.search.events.filter import convert_search_filter_to_snuba_query
+from sentry.search.events.filter import convert_search_filter_to_snuba_query, format_search_filter
 from sentry.services.hybrid_cloud.integration import integration_service
 from sentry.services.hybrid_cloud.notifications import notifications_service
 from sentry.services.hybrid_cloud.user import user_service
@@ -909,22 +909,32 @@ class GroupSerializerSnuba(GroupSerializerBase):
         if end_params:
             self.end = min(end_params)
 
-        self.conditions = (
-            [
-                convert_search_filter_to_snuba_query(
-                    search_filter,
-                    params={
-                        "organization_id": organization_id,
-                        "project_id": project_ids,
-                        "environment_id": environment_ids,
-                    },
-                )
-                for search_filter in search_filters
-                if search_filter.key.name not in self.skip_snuba_fields
-            ]
-            if search_filters is not None
-            else []
-        )
+        conditions = []
+        if search_filters is not None:
+            for search_filter in search_filters:
+                if search_filter.key.name not in self.skip_snuba_fields:
+                    formatted_conditions, projects_to_filter, group_ids = format_search_filter(
+                        search_filter,
+                        params={
+                            "organization_id": organization_id,
+                            "project_id": project_ids,
+                            "environment_id": environment_ids,
+                        },
+                    )
+                    # if no re-formatted conditions, use fallback method
+                    conditions.append(
+                        formatted_conditions[0]
+                        if formatted_conditions
+                        else convert_search_filter_to_snuba_query(
+                            search_filter,
+                            params={
+                                "organization_id": organization_id,
+                                "project_id": project_ids,
+                                "environment_id": environment_ids,
+                            },
+                        )
+                    )
+        self.conditions = conditions
 
     def _seen_stats_error(
         self, error_issue_list: Sequence[Group], user

+ 2 - 0
src/sentry/api/serializers/models/group_stream.py

@@ -176,6 +176,7 @@ class StreamGroupSerializerSnuba(GroupSerializerSnuba, GroupStatsMixin):
         collapse=None,
         expand=None,
         organization_id=None,
+        project_ids=None,
     ):
         super().__init__(
             environment_ids,
@@ -185,6 +186,7 @@ class StreamGroupSerializerSnuba(GroupSerializerSnuba, GroupStatsMixin):
             collapse=collapse,
             expand=expand,
             organization_id=organization_id,
+            project_ids=project_ids,
         )
 
         if stats_period is not None:

+ 2 - 2
src/sentry/search/events/filter.py

@@ -656,7 +656,7 @@ def convert_search_filter_to_snuba_query(
         # most field aliases are handled above but timestamp.to_{hour,day} are
         # handled here
         if name in FIELD_ALIASES:
-            name = FIELD_ALIASES[name].get_expression(params)
+            name = FIELD_ALIASES[name].get_field(params)
 
         # Tags are never null, but promoted tags are columns and so can be null.
         # To handle both cases, use `ifNull` to convert to an empty string and
@@ -979,7 +979,7 @@ def format_search_filter(term, params):
                     projects_to_filter = project_ids
                 conditions.append(converted_filter)
     elif name == ISSUE_ID_ALIAS and value != "":
-        # A blank term value means that this is a has filter
+        # A blank term value means that this is a 'has' filter
         if term.operator in EQUALITY_OPERATORS:
             group_ids = to_list(value)
         else:

+ 13 - 2
src/sentry/search/snuba/executors.py

@@ -45,7 +45,7 @@ from sentry.issues.search import (
 )
 from sentry.models import Environment, Group, Organization, Project
 from sentry.search.events.fields import DateArg
-from sentry.search.events.filter import convert_search_filter_to_snuba_query
+from sentry.search.events.filter import convert_search_filter_to_snuba_query, format_search_filter
 from sentry.search.utils import validate_cdc_search_filters
 from sentry.types.issues import PERFORMANCE_TYPES, GroupCategory, GroupType
 from sentry.utils import json, metrics, snuba
@@ -156,8 +156,19 @@ class AbstractQueryExecutor(metaclass=ABCMeta):
         """Converts the SearchFilter format into snuba-compatible clauses"""
         converted_filters: list[Optional[Sequence[Any]]] = []
         for search_filter in search_filters or ():
+            conditions, projects_to_filter, group_ids = format_search_filter(
+                search_filter,
+                params={
+                    "organization_id": organization_id,
+                    "project_id": project_ids,
+                    "environment": environments,
+                },
+            )
+            # if no re-formatted conditions, use fallback method
             converted_filters.append(
-                convert_search_filter_to_snuba_query(
+                conditions[0]
+                if conditions
+                else convert_search_filter_to_snuba_query(
                     search_filter,
                     params={
                         "organization_id": organization_id,

+ 45 - 0
tests/snuba/api/endpoints/test_organization_group_index.py

@@ -434,6 +434,21 @@ class GroupListTest(APITestCase, SnubaTestCase):
         response = self.get_response(environment="garbage")
         assert response.status_code == 404
 
+    def test_project(self):
+        self.store_event(
+            data={
+                "fingerprint": ["put-me-in-group1"],
+                "timestamp": iso_format(self.min_ago),
+                "environment": "production",
+            },
+            project_id=self.project.id,
+        )
+        project = self.project
+
+        self.login_as(user=self.user)
+        response = self.get_success_response(query=f"project:{project.slug}")
+        assert len(response.data) == 1
+
     def test_auto_resolved(self):
         project = self.project
         project.update_option("sentry:resolve_age", 1)
@@ -538,6 +553,36 @@ class GroupListTest(APITestCase, SnubaTestCase):
         response = self.get_success_response(query=short_id, shortIdLookup=1)
         assert len(response.data) == 1
 
+    def test_lookup_by_short_id_alias(self):
+        event_id = "f" * 32
+        group = self.store_event(
+            data={"event_id": event_id, "timestamp": iso_format(before_now(seconds=1))},
+            project_id=self.project.id,
+        ).group
+        short_id = group.qualified_short_id
+
+        self.login_as(user=self.user)
+        response = self.get_success_response(query=f"issue:{short_id}")
+        assert len(response.data) == 1
+
+    def test_lookup_by_multiple_short_id_alias(self):
+        self.login_as(self.user)
+        project = self.project
+        project2 = self.create_project(name="baz", organization=project.organization)
+        event = self.store_event(
+            data={"timestamp": iso_format(before_now(seconds=2))},
+            project_id=project.id,
+        )
+        event2 = self.store_event(
+            data={"timestamp": iso_format(before_now(seconds=1))},
+            project_id=project2.id,
+        )
+        with self.feature("organizations:global-views"):
+            response = self.get_success_response(
+                query=f"issue:[{event.group.qualified_short_id},{event2.group.qualified_short_id}]"
+            )
+        assert len(response.data) == 2
+
     def test_lookup_by_short_id_ignores_project_list(self):
         organization = self.create_organization()
         project = self.create_project(organization=organization)

+ 25 - 0
tests/snuba/api/endpoints/test_organization_group_index_stats.py

@@ -67,3 +67,28 @@ class GroupListTest(APITestCase, SnubaTestCase):
         self.login_as(user=self.user)
         response = self.get_response(sort_by="date", limit=10, query="is:unresolved", groups=[1337])
         assert response.status_code == 400
+
+    def test_simple_with_project(self):
+        self.store_event(
+            data={"timestamp": iso_format(before_now(seconds=500)), "fingerprint": ["group-1"]},
+            project_id=self.project.id,
+        )
+        group_a = self.store_event(
+            data={"timestamp": iso_format(before_now(seconds=1)), "fingerprint": ["group-a"]},
+            project_id=self.project.id,
+        ).group
+        self.store_event(
+            data={"timestamp": iso_format(before_now(seconds=2)), "fingerprint": ["group-b"]},
+            project_id=self.project.id,
+        )
+        group_c = self.store_event(
+            data={"timestamp": iso_format(before_now(seconds=3)), "fingerprint": ["group-c"]},
+            project_id=self.project.id,
+        ).group
+        self.login_as(user=self.user)
+        response = self.get_response(
+            query=f"project:{self.project.slug}", groups=[group_a.id, group_c.id]
+        )
+
+        assert response.status_code == 200
+        assert len(response.data) == 2