diff --git a/src/sentry/api/endpoints/organization_releases.py b/src/sentry/api/endpoints/organization_releases.py index b6b0ba83052eef..de98ae3948d53c 100644 --- a/src/sentry/api/endpoints/organization_releases.py +++ b/src/sentry/api/endpoints/organization_releases.py @@ -30,7 +30,12 @@ from sentry.models.activity import Activity from sentry.models.orgauthtoken import is_org_auth_token_auth, update_org_auth_token_last_used from sentry.models.project import Project -from sentry.models.release import Release, ReleaseStatus +from sentry.models.release import ( + Release, + ReleaseStatus, + filter_releases_by_environments, + filter_releases_by_projects, +) from sentry.models.releases.exceptions import ReleaseCommitError from sentry.models.releases.release_project import ReleaseProject from sentry.models.releases.util import SemverFilter @@ -307,7 +312,13 @@ def get(self, request: Request, organization) -> Response: # health data in the last 24 hours. debounce_update_release_health_data(organization, filter_params["project_id"]) - queryset = Release.objects.filter(organization=organization) + queryset = Release.objects.filter(organization_id=organization.id) + queryset = filter_releases_by_environments( + queryset, + filter_params["project_id"], + [e.id for e in filter_params.get("environment_objects", [])], + ) + queryset = queryset.annotate(date=F("date_added")) if status_filter: try: @@ -320,9 +331,6 @@ def get(self, request: Request, organization) -> Response: else: queryset = queryset.filter(status=status_int) - queryset = queryset.annotate(date=F("date_added")) - - queryset = add_environment_to_queryset(queryset, filter_params) if query: try: queryset = _filter_releases_by_query(queryset, organization, query, filter_params) @@ -333,12 +341,13 @@ def get(self, request: Request, organization) -> Response: ) select_extra = {} - - queryset = queryset.distinct() if flatten: + # NOTE: Do a deeper dive on this. What is this _for_project_id value? Was the author's + # intent that distinct would return multiple unique rows per project per release? + queryset = queryset.distinct().filter(projects__id__in=filter_params["project_id"]) select_extra["_for_project_id"] = "sentry_release_project.project_id" - - queryset = queryset.filter(projects__id__in=filter_params["project_id"]) + else: + queryset = filter_releases_by_projects(queryset, filter_params["project_id"]) if sort == "date": queryset = queryset.order_by("-date") @@ -664,20 +673,18 @@ def get(self, request: Request, organization) -> Response: except NoProjects: return Response([]) + queryset = Release.objects.filter(organization_id=organization.id) + queryset = add_date_filter_to_queryset(queryset, filter_params) + queryset = filter_releases_by_projects(queryset, filter_params["project_id"]) + queryset = filter_releases_by_environments( + queryset, + filter_params["project_id"], + [e.id for e in filter_params.get("environment_objects", [])], + ) queryset = ( - Release.objects.filter( - organization=organization, projects__id__in=filter_params["project_id"] - ) - .annotate( - date=F("date_added"), - ) - .values("version", "date") - .order_by("-date") - .distinct() + queryset.annotate(date=F("date_added")).values("version", "date").order_by("-date") ) - queryset = add_date_filter_to_queryset(queryset, filter_params) - queryset = add_environment_to_queryset(queryset, filter_params) if query: try: queryset = _filter_releases_by_query(queryset, organization, query, filter_params) diff --git a/src/sentry/models/release.py b/src/sentry/models/release.py index 0cb7e77a3a0a3a..b8268de62fd453 100644 --- a/src/sentry/models/release.py +++ b/src/sentry/models/release.py @@ -13,6 +13,7 @@ from django.utils import timezone from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ +from django_stubs_ext import QuerySetAny from sentry_relay.exceptions import RelayError from sentry_relay.processing import parse_release @@ -32,6 +33,7 @@ from sentry.db.models.manager.base import BaseManager from sentry.models.artifactbundle import ArtifactBundle from sentry.models.commitauthor import CommitAuthor +from sentry.models.releaseprojectenvironment import ReleaseProjectEnvironment from sentry.models.releases.constants import ( DB_VERSION_LENGTH, ERR_RELEASE_HEALTH_DATA, @@ -818,3 +820,38 @@ def get_previous_release(release: Release) -> Release | None: .order_by("-sort") .first() ) + + +def filter_releases_by_projects(queryset: QuerySetAny, project_ids: list[int]): + """Return releases belonging to a project.""" + if not project_ids: + return queryset + + return queryset.filter( + Exists( + ReleaseProject.objects.filter( + release=OuterRef("pk"), + project_id__in=project_ids, + ) + ) + ) + + +def filter_releases_by_environments( + queryset: QuerySetAny, + project_ids: list[int], + environment_ids: list[int], +): + """Return a release queryset filtered by environments.""" + if not environment_ids: + return queryset + + return queryset.filter( + Exists( + ReleaseProjectEnvironment.objects.filter( + release=OuterRef("pk"), + environment_id__in=environment_ids, + project_id__in=project_ids, + ) + ) + ) diff --git a/tests/sentry/api/endpoints/test_organization_releases.py b/tests/sentry/api/endpoints/test_organization_releases.py index a752753b7210bb..49aeca6282642f 100644 --- a/tests/sentry/api/endpoints/test_organization_releases.py +++ b/tests/sentry/api/endpoints/test_organization_releases.py @@ -325,7 +325,7 @@ def test_release_filter(self): release = Release.objects.create( organization_id=org.id, version="foobar", - date_added=datetime(2013, 8, 13, 3, 8, 24, 880386, tzinfo=UTC), + date_added=datetime(2013, 8, 13, 3, 8, 24, 880387, tzinfo=UTC), ) release.add_project(project)