Skip to content

Commit 756dbe7

Browse files
feat(releases): allow finalized query in releases endpoint (#87997)
closes #86258 allow query `finalized:true` or `finalized:false` on the `/releases/` endpoint a release is finalized if its `dateReleased` property is set.
1 parent 6c73bb7 commit 756dbe7

File tree

3 files changed

+47
-1
lines changed

3 files changed

+47
-1
lines changed

src/sentry/api/endpoints/organization_releases.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from sentry.api.bases.organization import OrganizationReleasesBaseEndpoint
1818
from sentry.api.exceptions import ConflictError, InvalidRepository
1919
from sentry.api.paginator import MergingOffsetPaginator, OffsetPaginator
20-
from sentry.api.release_search import RELEASE_FREE_TEXT_KEY, parse_search_query
20+
from sentry.api.release_search import FINALIZED_KEY, RELEASE_FREE_TEXT_KEY, parse_search_query
2121
from sentry.api.serializers import serialize
2222
from sentry.api.serializers.rest_framework import (
2323
ReleaseHeadCommitSerializer,
@@ -78,6 +78,12 @@ def add_date_filter_to_queryset(queryset, filter_params):
7878
def _filter_releases_by_query(queryset, organization, query, filter_params):
7979
search_filters = parse_search_query(query)
8080
for search_filter in search_filters:
81+
if search_filter.key.name == FINALIZED_KEY:
82+
if search_filter.value.value == "true":
83+
queryset = queryset.filter(date_released__isnull=False)
84+
elif search_filter.value.value == "false":
85+
queryset = queryset.filter(date_released__isnull=True)
86+
8187
if search_filter.key.name == RELEASE_FREE_TEXT_KEY:
8288
query_q = Q(version__icontains=query)
8389
suffix_match = _release_suffix.match(query)
@@ -267,6 +273,7 @@ def get(self, request: Request, organization) -> Response:
267273
health_stat = request.GET.get("healthStat") or "sessions"
268274
summary_stats_period = request.GET.get("summaryStatsPeriod") or "14d"
269275
health_stats_period = request.GET.get("healthStatsPeriod") or ("24h" if with_health else "")
276+
270277
if summary_stats_period not in STATS_PERIODS:
271278
raise ParseError(detail=get_stats_period_detail("summaryStatsPeriod", STATS_PERIODS))
272279
if health_stats_period and health_stats_period not in STATS_PERIODS:

src/sentry/api/release_search.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
)
1212

1313
RELEASE_FREE_TEXT_KEY = "release_free_text"
14+
FINALIZED_KEY = "finalized"
1415
INVALID_SEMVER_MESSAGE = (
1516
'Invalid format of semantic version. For searching non-semver releases, use "release:" instead.'
1617
)
@@ -23,6 +24,7 @@
2324
SEMVER_ALIAS,
2425
SEMVER_BUILD_ALIAS,
2526
SEMVER_PACKAGE_ALIAS,
27+
FINALIZED_KEY,
2628
},
2729
allow_boolean=False,
2830
free_text_key=RELEASE_FREE_TEXT_KEY,

tests/sentry/api/endpoints/test_organization_releases.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from django.utils import timezone
99

1010
from sentry.api.endpoints.organization_releases import ReleaseSerializerWithProjects
11+
from sentry.api.release_search import FINALIZED_KEY
1112
from sentry.api.serializers.rest_framework.release import ReleaseHeadCommitSerializer
1213
from sentry.auth import access
1314
from sentry.constants import BAD_RELEASE_CHARS, MAX_COMMIT_LENGTH, MAX_VERSION_LENGTH
@@ -1067,6 +1068,42 @@ def test_semver_filter(self):
10671068
)
10681069
assert [r["version"] for r in response.data] == [release_2.version, release_1.version]
10691070

1071+
def test_finalized_filter(self):
1072+
self.login_as(user=self.user)
1073+
1074+
release_1 = self.create_release(
1075+
version="[email protected]", date_released=datetime(2013, 8, 13, 3, 8, 24, 880386, tzinfo=UTC)
1076+
)
1077+
release_2 = self.create_release(version="[email protected]", date_released=None)
1078+
release_3 = self.create_release(version="[email protected]", date_released=None)
1079+
release_4 = self.create_release(version="[email protected]")
1080+
1081+
url = reverse(
1082+
"sentry-api-0-organization-releases",
1083+
kwargs={"organization_id_or_slug": self.organization.slug},
1084+
)
1085+
response = self.client.get(url + f"?query={FINALIZED_KEY}:true", format="json")
1086+
1087+
assert response.status_code == 200, response.content
1088+
assert len(response.data) == 1
1089+
assert [r["version"] for r in response.data] == [release_1.version]
1090+
1091+
response = self.client.get(url + f"?query={FINALIZED_KEY}:false", format="json")
1092+
assert [r["version"] for r in response.data] == [
1093+
release_4.version,
1094+
release_3.version,
1095+
release_2.version,
1096+
]
1097+
1098+
# if anything besides "true" or "false" is parsed, return all releases
1099+
response = self.client.get(url + f"?query={FINALIZED_KEY}:wrong_value", format="json")
1100+
assert [r["version"] for r in response.data] == [
1101+
release_4.version,
1102+
release_3.version,
1103+
release_2.version,
1104+
release_1.version,
1105+
]
1106+
10701107
def test_release_stage_filter(self):
10711108
self.login_as(user=self.user)
10721109

0 commit comments

Comments
 (0)