diff --git a/src/sentry/api/endpoints/organization_releases.py b/src/sentry/api/endpoints/organization_releases.py index 0ae1266b9f39a3..d080b689012205 100644 --- a/src/sentry/api/endpoints/organization_releases.py +++ b/src/sentry/api/endpoints/organization_releases.py @@ -24,7 +24,12 @@ MergingOffsetPaginator, OffsetPaginator, ) -from sentry.api.release_search import FINALIZED_KEY, RELEASE_FREE_TEXT_KEY, parse_search_query +from sentry.api.release_search import ( + FINALIZED_KEY, + RELEASE_CREATED_KEY, + RELEASE_FREE_TEXT_KEY, + parse_search_query, +) from sentry.api.serializers import serialize from sentry.api.serializers.rest_framework import ( ReleaseHeadCommitSerializer, @@ -166,6 +171,13 @@ def _filter_releases_by_query(queryset, organization, query, filter_params): negated=negated, ) + if search_filter.key.name == RELEASE_CREATED_KEY: + queryset = queryset.filter( + **{ + f"date_added__{OPERATOR_TO_DJANGO[search_filter.operator]}": search_filter.value.raw_value + } + ) + return queryset diff --git a/src/sentry/api/release_search.py b/src/sentry/api/release_search.py index 9a498a256db196..63bbbc3441d47b 100644 --- a/src/sentry/api/release_search.py +++ b/src/sentry/api/release_search.py @@ -12,6 +12,7 @@ RELEASE_FREE_TEXT_KEY = "release_free_text" FINALIZED_KEY = "finalized" +RELEASE_CREATED_KEY = "release.created" INVALID_SEMVER_MESSAGE = ( 'Invalid format of semantic version. For searching non-semver releases, use "release:" instead.' ) @@ -25,7 +26,9 @@ SEMVER_BUILD_ALIAS, SEMVER_PACKAGE_ALIAS, FINALIZED_KEY, + RELEASE_CREATED_KEY, }, + date_keys={RELEASE_CREATED_KEY}, allow_boolean=False, free_text_key=RELEASE_FREE_TEXT_KEY, ) diff --git a/tests/sentry/api/endpoints/test_organization_releases.py b/tests/sentry/api/endpoints/test_organization_releases.py index 84d0db533a0216..722827f0b71fa1 100644 --- a/tests/sentry/api/endpoints/test_organization_releases.py +++ b/tests/sentry/api/endpoints/test_organization_releases.py @@ -8,7 +8,7 @@ from django.utils import timezone from sentry.api.endpoints.organization_releases import ReleaseSerializerWithProjects -from sentry.api.release_search import FINALIZED_KEY +from sentry.api.release_search import FINALIZED_KEY, RELEASE_CREATED_KEY from sentry.api.serializers.rest_framework.release import ReleaseHeadCommitSerializer from sentry.auth import access from sentry.constants import BAD_RELEASE_CHARS, MAX_COMMIT_LENGTH, MAX_VERSION_LENGTH @@ -1097,6 +1097,45 @@ def test_finalized_filter(self) -> None: release_1.version, ] + def test_release_created_filter(self) -> None: + self.login_as(user=self.user) + + now = timezone.now() + one_day_ago = now - timedelta(days=1) + one_week_ago = now - timedelta(days=7) + two_weeks_ago = now - timedelta(days=14) + + release_1 = self.create_release(version="release-1", date_added=two_weeks_ago) + release_2 = self.create_release(version="release-2", date_added=one_week_ago) + release_3 = self.create_release(version="release-3", date_added=one_day_ago) + release_4 = self.create_release(version="release-4", date_added=now) + + # Test relative date filter: releases created in the last 3 days + response = self.get_success_response( + self.organization.slug, query=f"{RELEASE_CREATED_KEY}:-3d" + ) + assert [r["version"] for r in response.data] == [ + release_4.version, + release_3.version, + ] + + # Test comparison operator: releases created after a specific date + formatted_date = one_week_ago.strftime("%Y-%m-%dT%H:%M:%S") + response = self.get_success_response( + self.organization.slug, query=f"{RELEASE_CREATED_KEY}:>={formatted_date}" + ) + assert [r["version"] for r in response.data] == [ + release_4.version, + release_3.version, + release_2.version, + ] + + # Test comparison operator: releases created before a specific date + response = self.get_success_response( + self.organization.slug, query=f"{RELEASE_CREATED_KEY}:<{formatted_date}" + ) + assert [r["version"] for r in response.data] == [release_1.version] + def test_release_stage_filter(self) -> None: self.login_as(user=self.user)