Skip to content

Conversation

@mishamo
Copy link

@mishamo mishamo commented Jan 18, 2026

Summary

Adds a new team: search filter that allows filtering issues by the team that owns the project. This addresses #45367.

Usage:

  • team:data-engineering - Issues from projects owned by data-engineering
  • team:#data-engineering - Same (# prefix supported for consistency with other team references)
  • !team:data-engineering - Issues NOT from data-engineering's projects
  • team:[team1,team2] - Issues from projects owned by either team

Changes

  • src/sentry/issues/issue_search.py:
    • Add team key mapping
    • Add convert_team_value() converter that validates teams through projectteam relationship (prevents IDOR by ensuring users can only filter by teams associated with their searched projects)
  • src/sentry/search/snuba/backend.py:
    • Add team_filter() function
    • Add team condition to queryset builder
  • tests/snuba/search/test_backend.py:
    • test_team_filter - Basic filtering with/without # prefix
    • test_team_filter_negation - !team: exclusion
    • test_team_filter_invalid_team - Error handling for non-existent teams
    • test_team_filter_multiple_teams - team:[t1,t2] syntax
    • test_team_filter_team_not_in_searched_projects - Security test for IDOR prevention

Test plan

  • Filter by team slug returns only issues from that team's projects
  • # prefix works for consistency with other team references
  • Negation (!team:) excludes issues from team's projects
  • Invalid team raises InvalidSearchQuery
  • Multiple teams syntax works
  • Teams not associated with searched projects raise error (IDOR prevention)

🤖 Generated with Claude Code

@mishamo mishamo requested review from a team as code owners January 18, 2026 12:55
@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Jan 18, 2026
@mishamo mishamo requested a review from a team as a code owner January 18, 2026 16:22
@mishamo mishamo force-pushed the feat/team-issue-filter branch from 5aba036 to 81d8ffc Compare January 18, 2026 17:27
@mishamo
Copy link
Author

mishamo commented Jan 18, 2026

bugbot review

@mishamo
Copy link
Author

mishamo commented Jan 18, 2026

@sentry review

cursor[bot]

This comment was marked as outdated.

@mishamo
Copy link
Author

mishamo commented Jan 19, 2026

Is there anything else I have to do in the PR process here? (I can't find any docs). Or is this just waiting for a maintainer to take a look?

@cvxluo
Copy link
Contributor

cvxluo commented Jan 20, 2026

Is there anything else I have to do in the PR process here? (I can't find any docs). Or is this just waiting for a maintainer to take a look?

you're all good, i'll take a look.

@cvxluo cvxluo added the Trigger: getsentry tests Once code is reviewed: apply label to PR to trigger getsentry tests label Jan 20, 2026
@scttcper
Copy link
Member

this likely would need to take into account the org open membership settings

@mishamo
Copy link
Author

mishamo commented Jan 21, 2026

Thanks for the feedback. I noticed the existing parse_team_value used by assigned_to also only filters by project association, not org membership settings.

A few questions:

  1. Should this be consistent with assigned_to behavior?
  2. Is this a broader issue to fix across all team-related filters?
  3. Would you prefer we use has_team_access checks here, or is project-based filtering sufficient given users already need project access?
@github-actions github-actions bot removed the Trigger: getsentry tests Once code is reviewed: apply label to PR to trigger getsentry tests label Jan 22, 2026
cursor[bot]

This comment was marked as outdated.

@mishamo mishamo force-pushed the feat/team-issue-filter branch from f8d2c23 to 782cf90 Compare January 23, 2026 09:14
cursor[bot]

This comment was marked as outdated.

Comment on lines 272 to 296
def convert_team_value(
value: Iterable[str],
projects: Sequence[Project],
user: User,
environments: Sequence[Environment] | None,
) -> list[RpcUser | Team | None]:
"""Convert team slug(s) to Team objects for filtering issues by project ownership.

Only returns teams that are associated with at least one of the searched projects,
ensuring users can only filter by teams they have visibility to.
"""
if not projects:
return []
teams: list[RpcUser | Team | None] = []
for team_identifier in value:
team_slug = team_identifier.lstrip("#")
team = Team.objects.filter(
slug__iexact=team_slug,
organization_id=projects[0].organization_id,
projectteam__project__in=projects,
).first()
if not team:
raise InvalidSearchQuery(f"Invalid team '{team_identifier}'")
teams.append(team)
return teams
Copy link
Member

Choose a reason for hiding this comment

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

we can probably bulk query and i think that IDOR warning isn't really fixed with project[0].organization_id any more than using in=projects so lets keep it just filtering by projects.

Suggested change
def convert_team_value(
value: Iterable[str],
projects: Sequence[Project],
user: User,
environments: Sequence[Environment] | None,
) -> list[RpcUser | Team | None]:
"""Convert team slug(s) to Team objects for filtering issues by project ownership.
Only returns teams that are associated with at least one of the searched projects,
ensuring users can only filter by teams they have visibility to.
"""
if not projects:
return []
teams: list[RpcUser | Team | None] = []
for team_identifier in value:
team_slug = team_identifier.lstrip("#")
team = Team.objects.filter(
slug__iexact=team_slug,
organization_id=projects[0].organization_id,
projectteam__project__in=projects,
).first()
if not team:
raise InvalidSearchQuery(f"Invalid team '{team_identifier}'")
teams.append(team)
return teams
def convert_team_value(
value: Iterable[str],
projects: Sequence[Project],
user: User,
environments: Sequence[Environment] | None,
) -> list[RpcUser | Team | None]:
if not projects:
return []
slugs = [v[1:] if v.startswith("#") else v for v in value]
if not slugs:
return []
teams = Team.objects.filter(
slug__in=slugs,
projectteam__project__in=projects,
).distinct()
teams_by_slug = {team.slug: team for team in teams}
result: list[RpcUser | Team | None] = []
for slug in slugs:
team = teams_by_slug.get(slug)
if not team:
raise InvalidSearchQuery(f"Invalid team '{slug}'")
result.append(team)
return result
@scttcper
Copy link
Member

@mishamo never mind on the open membership we should already be filtering down projects to the ones available to user user similar to assigned to.

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 is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Adds a new `team:` search filter that allows filtering issues by the team
that owns the project. This enables searching for issues like `team:platform`
to find all issues in projects owned by the platform team.

Implementation:
- Added `team` to `key_mappings` in issue search config
- Added `convert_team_value` to convert team slugs to Team objects
- Added `team_filter` QCallbackCondition in snuba backend
- Added `team` to NO_CONVERSION_FIELDS to skip Snuba conversion
- Uses case-insensitive slug matching consistent with existing patterns

Closes getsentry#45367

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mishamo mishamo force-pushed the feat/team-issue-filter branch from 7d7accf to 6c9f1c4 Compare January 24, 2026 19:09
@mishamo
Copy link
Author

mishamo commented Jan 27, 2026

@cvxluo @scttcper Any more thoughts on this?

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

3 participants