diff --git a/src/sentry/api/endpoints/organization_group_index.py b/src/sentry/api/endpoints/organization_group_index.py index ad45b238694f5c..48942f903c0565 100644 --- a/src/sentry/api/endpoints/organization_group_index.py +++ b/src/sentry/api/endpoints/organization_group_index.py @@ -43,6 +43,7 @@ from sentry.utils.validators import normalize_event_id ERR_INVALID_STATS_PERIOD = "Invalid stats_period. Valid choices are '', '24h', and '14d'" +DEFAULT_REFERRER = "search.group_index" allowed_inbox_search_terms = frozenset(["date", "status", "for_review", "assigned_or_suggested"]) @@ -165,7 +166,13 @@ class OrganizationGroupIndexEndpoint(OrganizationEventsEndpointBase): } def _search( - self, request: Request, organization, projects, environments, extra_query_kwargs=None + self, + request: Request, + organization, + projects, + environments, + referrer, + extra_query_kwargs=None, ): with start_span(op="_search"): query_kwargs = build_query_params_from_request( @@ -182,7 +189,7 @@ def _search( query_kwargs.pop("sort_by") result = inbox_search(**query_kwargs) else: - query_kwargs["referrer"] = "search.group_index" + query_kwargs["referrer"] = referrer result = search.query(**query_kwargs) return result, query_kwargs @@ -234,6 +241,7 @@ def get(self, request: Request, organization) -> Response: :qparam list collapse: an optional list of strings to opt out of certain pieces of data. Supports `stats`, `lifetime`, `base`, `unhandled` """ stats_period = request.GET.get("groupStatsPeriod") + referrer = request.GET.get("referrer", DEFAULT_REFERRER) try: start, end = get_date_range_from_stats_period(request.GET) except InvalidParams as e: @@ -335,6 +343,7 @@ def get(self, request: Request, organization) -> Response: organization, projects, environments, + referrer, {"count_hits": True, "date_to": end, "date_from": start}, ) except (ValidationError, discover.InvalidSearchQuery) as exc: @@ -440,6 +449,7 @@ def put(self, request: Request, organization) -> Response: """ projects = self.get_projects(request, organization) is_fetching_replay_data = request.headers.get("X-Sentry-Replay-Request") == "1" + referrer = request.GET.get("referrer", DEFAULT_REFERRER) if ( len(projects) > 1 @@ -456,6 +466,7 @@ def put(self, request: Request, organization) -> Response: organization, projects, self.get_environments(request, organization), + referrer, ) return update_groups( @@ -486,7 +497,7 @@ def delete(self, request: Request, organization) -> Response: :auth: required """ projects = self.get_projects(request, organization) - + referrer = request.GET.get("referrer", DEFAULT_REFERRER) is_fetching_replay_data = request.headers.get("X-Sentry-Replay-Request") == "1" if ( @@ -504,6 +515,7 @@ def delete(self, request: Request, organization) -> Response: organization, projects, self.get_environments(request, organization), + referrer, ) return delete_groups(request, projects, organization.id, search_fn) diff --git a/src/sentry/issues/search.py b/src/sentry/issues/search.py index a7a78315a85de2..59a7898ce94126 100644 --- a/src/sentry/issues/search.py +++ b/src/sentry/issues/search.py @@ -211,6 +211,7 @@ def _query_params_for_generic( conditions: Sequence[Any], actor: Optional[Any] = None, categories: Optional[Sequence[GroupCategory]] = None, + referrer: str = None, ) -> Optional[SnubaQueryParams]: organization = Organization.objects.filter(id=organization_id).first() if organization and features.has( @@ -219,8 +220,9 @@ def _query_params_for_generic( if categories is None: logging.error("Category is required in _query_params_for_generic") return None - category_ids = {gc.value for gc in categories} + if referrer != "api.feedback_index": + category_ids.discard(GroupCategory.FEEDBACK.value) group_types = { gt.type_id for gt in grouptype.registry.get_visible(organization, actor) @@ -248,13 +250,15 @@ def _query_params_for_generic( return None -def get_search_strategies() -> Mapping[int, GroupSearchStrategy]: +def get_search_strategies(referrer: str) -> Mapping[int, GroupSearchStrategy]: strategies = {} for group_category in GroupCategory: if group_category == GroupCategory.ERROR: strategy = _query_params_for_error else: - strategy = functools.partial(_query_params_for_generic, categories=[group_category]) + strategy = functools.partial( + _query_params_for_generic, categories=[group_category], referrer=referrer + ) strategies[group_category.value] = strategy return strategies diff --git a/src/sentry/search/snuba/executors.py b/src/sentry/search/snuba/executors.py index ca00cf4d6201bc..7ff2a4efbdeaa5 100644 --- a/src/sentry/search/snuba/executors.py +++ b/src/sentry/search/snuba/executors.py @@ -280,6 +280,7 @@ def _prepare_params_for_category( get_sample: bool, actor: Optional[Any] = None, aggregate_kwargs: Optional[PrioritySortWeights] = None, + referrer: Optional[str] = None, ) -> SnubaQueryParams: """ :raises UnsupportedSearchQuery: when search_filters includes conditions on a dataset that doesn't support it @@ -336,8 +337,7 @@ def _prepare_params_for_category( orderby=orderby, ), ) - - strategy = get_search_strategies()[group_category] + strategy = get_search_strategies(referrer)[group_category] snuba_query_params = strategy( pinned_query_partial, selected_columns, @@ -379,7 +379,6 @@ def snuba_search( * the count of total results (rows) available for this query. """ filters = {"project_id": project_ids} - environments = None if environment_ids is not None: filters["environment"] = environment_ids @@ -420,7 +419,7 @@ def snuba_search( if not group_categories: group_categories = { gc - for gc in get_search_strategies().keys() + for gc in get_search_strategies(referrer).keys() if gc != GroupCategory.PROFILE.value or features.has("organizations:issue-platform", organization, actor=actor) } @@ -448,6 +447,7 @@ def snuba_search( get_sample, actor, aggregate_kwargs, + referrer, ) except UnsupportedSearchQuery: pass diff --git a/tests/snuba/search/test_backend.py b/tests/snuba/search/test_backend.py index d58be7d37eab14..226dc7fac4fd07 100644 --- a/tests/snuba/search/test_backend.py +++ b/tests/snuba/search/test_backend.py @@ -14,6 +14,7 @@ from sentry.exceptions import InvalidSearchQuery from sentry.issues.grouptype import ( ErrorGroupType, + FeedbackGroup, NoiseConfig, PerformanceNPlusOneGroupType, PerformanceRenderBlockingAssetSpanGroupType, @@ -73,6 +74,7 @@ def make_query( date_from=None, date_to=None, cursor=None, + referrer=None, aggregate_kwargs=None, ): search_filters = [] @@ -87,7 +89,6 @@ def make_query( kwargs["limit"] = limit if aggregate_kwargs: kwargs["aggregate_kwargs"] = {"priority": {**aggregate_kwargs}} - return self.backend.query( projects, search_filters=search_filters, @@ -97,6 +98,7 @@ def make_query( date_from=date_from, date_to=date_to, cursor=cursor, + referrer=referrer, **kwargs, ) @@ -3684,6 +3686,94 @@ def test_rejected_filters(self): == [] ) + def test_feedback_category_hidden_default(self): + with self.feature( + ["organizations:issue-platform", FeedbackGroup.build_visible_feature_name()] + ): + event_id_1 = uuid.uuid4().hex + _, group_info = process_event_and_issue_occurrence( + { + "id": uuid.uuid4().hex, + "project_id": 1, + "event_id": event_id_1, + "fingerprint": ["nonsense"], + "issue_title": "User Feedback", + "subtitle": "it was bad", + "culprit": "api/123", + "resource_id": "1234", + "evidence_data": {"Test": 123}, + "evidence_display": [ + {"name": "hi", "value": "bye", "important": True}, + {"name": "what", "value": "where", "important": False}, + ], + "type": FeedbackGroup.type_id, + "detection_time": datetime.now().timestamp(), + "level": "info", + }, + { + "event_id": event_id_1, + "project_id": self.project.id, + "title": "some problem", + "platform": "python", + "tags": {"my_tag": "1"}, + "timestamp": before_now(minutes=1).isoformat(), + "received": before_now(minutes=1).isoformat(), + }, + ) + results = self.make_query( + search_filter_query="issue.category:feedback", + date_from=self.base_datetime, + date_to=self.base_datetime + timedelta(days=10), + ) + assert set(results) == set() + + def test_feedback_category_show_when_referrer_set(self): + with self.feature( + [ + "organizations:issue-platform", + FeedbackGroup.build_visible_feature_name(), + FeedbackGroup.build_ingest_feature_name(), + ] + ): + event_id_1 = uuid.uuid4().hex + _, group_info = process_event_and_issue_occurrence( + { + "id": uuid.uuid4().hex, + "project_id": 1, + "event_id": event_id_1, + "fingerprint": ["nonsense"], + "issue_title": "User Feedback", + "subtitle": "it was bad", + "culprit": "api/123", + "resource_id": "1234", + "evidence_data": {"Test": 123}, + "evidence_display": [ + {"name": "hi", "value": "bye", "important": True}, + {"name": "what", "value": "where", "important": False}, + ], + "type": FeedbackGroup.type_id, + "detection_time": datetime.now().timestamp(), + "level": "info", + }, + { + "event_id": event_id_1, + "project_id": self.project.id, + "title": "some problem", + "platform": "python", + "tags": {"my_tag": "1"}, + "timestamp": before_now(minutes=1).isoformat(), + "received": before_now(minutes=1).isoformat(), + }, + ) + results = self.make_query( + search_filter_query="issue.category:feedback", + referrer="api.feedback_index", + date_from=self.base_datetime, + date_to=self.base_datetime + timedelta(days=10), + ) + assert group_info is not None + assert list(results) == [group_info.group] + class CdcEventsSnubaSearchTest(TestCase, SharedSnubaMixin): @property