Skip to content

fix(traces): Filter unreleased issue types from trace API#110356

Merged
nora-shap merged 3 commits intomasterfrom
nora/ID-1380
Mar 11, 2026
Merged

fix(traces): Filter unreleased issue types from trace API#110356
nora-shap merged 3 commits intomasterfrom
nora/ID-1380

Conversation

@nora-shap
Copy link
Member

@nora-shap nora-shap commented Mar 10, 2026

When developing a new performance issue type, we add it with released = False, which allows us to test and calibrate without exposing the resulting "test issues" to users.

Last week I observed events of unreleased issue types were appearing in multiple trace-related views in prod:

  • Trace waterfall
  • Trace preview timeline + "1 other issue" display on issue details
  • Event graph in issue details

Users could see issues in traces that should have been hidden. Unreleased issue types are not returned in /issues/ api responses, but the same filtering was not applied to the trace API.

I implemented these 2 prs: #109857 and #109929 (which can be reverted once this is landed, since the changes in this pr are a better and more broad solution).

Solution

Add backend filtering to the trace API (/organizations/{org}/trace/{trace_id}/) using the same visibility system that the issues API uses. The trace query now calls grouptype_registry.get_visible(organization) to filter performance occurrences by visible issue types, which:

  1. Includes all released=True issue types
  2. Includes unreleased types only if the org has the corresponding organizations:issue-{slug}-visible feature flag enabled

Changes

  • src/sentry/snuba/trace.py: Add organization param to _perf_issues_query and filter by visible occurrence type IDs
  • src/sentry/api/endpoints/organization_trace.py: Thread organization through to the query
  • Update all callers (autofix, trace summary, seer explorer) to pass organization

Notes

  • This only works for performance issues, which have the occurrence_type_id field on the events
  • This allows reverting PR fix(traces): Hide LLM Detected issues from trace details view #109929 which added frontend filtering as a stopgap
  • The get_visible() call is already used in the issues search endpoint, so performance characteristics are known
  • Sentry SDK span already exists for monitoring (GroupTypeRegistry.get_visible)

@linear-code
Copy link

linear-code bot commented Mar 10, 2026

@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Mar 10, 2026
Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Empty visible types list skips filtering instead of blocking
    • Changed logic to filter by impossible occurrence_type_id (-1) when visible types list is empty, ensuring no results are returned instead of all occurrences.

Create PR

Or push these changes by commenting:

@cursor push a41492a9b0
Preview (a41492a9b0)
diff --git a/src/sentry/snuba/trace.py b/src/sentry/snuba/trace.py
--- a/src/sentry/snuba/trace.py
+++ b/src/sentry/snuba/trace.py
@@ -334,7 +334,9 @@
 
     if organization:
         visible_type_ids = [gt.type_id for gt in grouptype_registry.get_visible(organization)]
-        if visible_type_ids:
+        if not visible_type_ids:
+            query = f"trace:{trace_id} occurrence_type_id:[-1]"
+        else:
             query = f"trace:{trace_id} occurrence_type_id:[{','.join(map(str, visible_type_ids))}]"
 
     occurrence_query = DiscoverQueryBuilder(

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

if organization:
visible_type_ids = [gt.type_id for gt in grouptype_registry.get_visible(organization)]
if visible_type_ids:
query = f"trace:{trace_id} occurrence_type_id:[{','.join(map(str, visible_type_ids))}]"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty visible types list skips filtering instead of blocking

Medium Severity

When organization is provided but get_visible(organization) returns an empty list, the if visible_type_ids check is falsy, so the code falls back to the unfiltered query (trace:{trace_id}), returning all occurrences instead of none. The existing search code in src/sentry/issues/search.py handles this correctly by returning None (no results) when group_types is empty. This inverted fallback means that if no issue types are visible for an org, every occurrence is shown rather than hidden.

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is true: if for some reason get_visible() is unavailable (should be very rare), we fallback to the original behavior, which is NO filter and "unreleased" occurrences would be included in the response. I thought this was better than returning None, but LMK if you disagree.

# decide if this is released.
# decide if this is released. Add to HIDDEN_ISSUE_TYPES as well to prevent Events from this Group
# being displayed on frontend.
released: bool = False
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because released is stored in code (not db or options), the frontend cannot access it directly, which it needs to do for things like search autocomplete. Therefore, a copy of this needs to be maintained in a place in code that frontend can access:

const HIDDEN_ISSUE_TYPES: IssueType[] = [

@nora-shap nora-shap marked this pull request as ready for review March 10, 2026 21:23
@nora-shap nora-shap requested review from a team as code owners March 10, 2026 21:23
Copy link
Member

@wmak wmak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one question about an arg change, but lgtm

error_id: str | None = None,
additional_attributes: list[str] | None = None,
include_uptime: bool = False,
*,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason we're adding * as an arg?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It forces the args after it to be a kwarg, so organization must be passed as a kwarg.
I like kwargs more than positional args, and it was easier to update the functions that call this one.

Additionally, organization is required but it comes after several optional args - without the *, I would have to give it a default value since params without a default cannot follow a param with default.

But, i do see how this breaks with current style, so happy to remove if you think that's best!

@nora-shap nora-shap merged commit 0fd63d2 into master Mar 11, 2026
80 checks passed
@nora-shap nora-shap deleted the nora/ID-1380 branch March 11, 2026 21:49
nora-shap added a commit that referenced this pull request Mar 12, 2026
…110565)

partially reverts #109929 - the
issue type filtering is now done at the api
(#110356) which makes the
frontend filtering redundant.

removes:
- `addOccurrence()` method and its `HIDDEN_OCCURRENCE_TYPE_IDS`
filtering
- The test for `addOccurrence` filtering
- All `addOccurrence()` calls → back to `occurrences.add()`

keeps:
- `EAPOccurrence.issue_type` field name (bug fix)
- anrRootCause.tsx handling of both `type` and `issue_type`
- Unfortunately, `TraceOccurrence` = `TracePerformanceIssue |
EAPOccurrence`. `TracePerformanceIssue` uses `type` while
`EAPOccurrence` now uses `issue_type` so both fields need to be checked
on a `TraceOccurrence`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants