diff --git a/src/sentry/api/endpoints/organization_releases.py b/src/sentry/api/endpoints/organization_releases.py index 7cb2b50975b638..0124c1b2fcd276 100644 --- a/src/sentry/api/endpoints/organization_releases.py +++ b/src/sentry/api/endpoints/organization_releases.py @@ -42,7 +42,6 @@ from sentry.signals import release_created from sentry.snuba.sessions import ( STATS_PERIODS, - get_oldest_health_data_for_releases, get_project_releases_by_stability, get_project_releases_count, ) @@ -182,7 +181,7 @@ def debounce_update_release_health_data(organization, project_ids): to_upsert.append(key) if to_upsert: - dates = get_oldest_health_data_for_releases(to_upsert) + dates = release_health.get_oldest_health_data_for_releases(to_upsert) for project_id, version in to_upsert: project = projects.get(project_id) diff --git a/src/sentry/api/endpoints/project_release_stats.py b/src/sentry/api/endpoints/project_release_stats.py index 9bfa437fc8fafc..f205a4d8d02645 100644 --- a/src/sentry/api/endpoints/project_release_stats.py +++ b/src/sentry/api/endpoints/project_release_stats.py @@ -1,14 +1,11 @@ from rest_framework.response import Response +from sentry import release_health from sentry.api.bases.project import ProjectEndpoint, ProjectEventsError, ProjectReleasePermission from sentry.api.exceptions import ResourceDoesNotExist from sentry.api.serializers import serialize from sentry.models import Release, ReleaseProject -from sentry.snuba.sessions import ( - get_crash_free_breakdown, - get_oldest_health_data_for_releases, - get_project_release_stats, -) +from sentry.snuba.sessions import get_crash_free_breakdown, get_project_release_stats from sentry.utils.dates import get_rollup_from_request @@ -17,7 +14,7 @@ def upsert_missing_release(project, version): try: return ReleaseProject.objects.get(project=project, release__version=version).release except ReleaseProject.DoesNotExist: - rows = get_oldest_health_data_for_releases([(project.id, version)]) + rows = release_health.get_oldest_health_data_for_releases([(project.id, version)]) if rows: oldest = next(rows.values()) release = Release.get_or_create(project=project, version=version, date_added=oldest) diff --git a/src/sentry/release_health/base.py b/src/sentry/release_health/base.py index 0f72267e63d740..5e892b698fe3f1 100644 --- a/src/sentry/release_health/base.py +++ b/src/sentry/release_health/base.py @@ -64,6 +64,7 @@ class ReleaseHealthBackend(Service): # type: ignore "get_release_sessions_time_bounds", "check_releases_have_health_data", "get_changed_project_release_model_adoptions", + "get_oldest_health_data_for_releases", ) def get_current_and_previous_crash_free_rates( @@ -186,3 +187,11 @@ def get_changed_project_release_model_adoptions( releases seen in the last 72 hours for the requested projects. """ raise NotImplementedError() + + def get_oldest_health_data_for_releases( + self, project_releases: Sequence[ProjectRelease] + ) -> Mapping[ProjectRelease, str]: + """Returns the oldest health data we have observed in a release + in 90 days. This is used for backfilling. + """ + raise NotImplementedError() diff --git a/src/sentry/release_health/metrics.py b/src/sentry/release_health/metrics.py index db1e9c4b70990b..910fb285e4d130 100644 --- a/src/sentry/release_health/metrics.py +++ b/src/sentry/release_health/metrics.py @@ -652,3 +652,64 @@ def extract_row_info(row: Mapping[str, Union[OrganizationId, str]]) -> ProjectRe return row.get("project_id"), reverse_tag_value(org_id, row.get(release_column_name)) # type: ignore return [extract_row_info(row) for row in result["data"]] + + def get_oldest_health_data_for_releases( + self, + project_releases: Sequence[ProjectRelease], + ) -> Mapping[ProjectRelease, str]: + + now = datetime.now(pytz.utc) + start = now - timedelta(days=90) + + project_ids: List[ProjectId] = [x[0] for x in project_releases] + org_id = self._get_org_id(project_ids) + release_column_name = tag_key(org_id, "release") + releases = [x[1] for x in project_releases] + releases_ids = [ + release_id + for release_id in [try_get_tag_value(org_id, release) for release in releases] + if release_id is not None + ] + + query_cols = [ + Column("project_id"), + Column(release_column_name), + Function("min", [Column("bucketed_time")], "oldest"), + ] + + group_by = [ + Column("project_id"), + Column(release_column_name), + ] + + where_clause = [ + Condition(Column("org_id"), Op.EQ, org_id), + Condition(Column("project_id"), Op.IN, project_ids), + Condition(Column("metric_id"), Op.EQ, metric_id(org_id, "session")), + Condition(Column("timestamp"), Op.GTE, start), + Condition(Column("timestamp"), Op.LT, now), + Condition(Column(release_column_name), Op.IN, releases_ids), + ] + + query = Query( + dataset=Dataset.Metrics.value, + match=Entity("metrics_counters"), + select=query_cols, + where=where_clause, + groupby=group_by, + granularity=Granularity(3600), + ) + rows = raw_snql_query( + query, + referrer="release_health.metrics.get_oldest_health_data_for_releases", + use_cache=False, + )["data"] + + result = {} + + for row in rows: + result[row["project_id"], reverse_tag_value(org_id, row[release_column_name])] = row[ + "oldest" + ] + + return result diff --git a/src/sentry/release_health/sessions.py b/src/sentry/release_health/sessions.py index b84964b2b4ca10..c0ca42904acf04 100644 --- a/src/sentry/release_health/sessions.py +++ b/src/sentry/release_health/sessions.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Optional, Sequence, Set, Tuple +from typing import Mapping, Optional, Sequence, Set, Tuple from sentry.release_health.base import ( CurrentAndPreviousCrashFreeRates, @@ -17,6 +17,7 @@ _check_has_health_data, _check_releases_have_health_data, _get_changed_project_release_model_adoptions, + _get_oldest_health_data_for_releases, _get_release_adoption, _get_release_sessions_time_bounds, get_current_and_previous_crash_free_rates, @@ -93,3 +94,9 @@ def get_changed_project_release_model_adoptions( project_ids: Sequence[ProjectId], ) -> Sequence[ProjectRelease]: return _get_changed_project_release_model_adoptions(project_ids) # type: ignore + + def get_oldest_health_data_for_releases( + self, + project_releases: Sequence[ProjectRelease], + ) -> Mapping[ProjectRelease, str]: + return _get_oldest_health_data_for_releases(project_releases) # type: ignore diff --git a/src/sentry/snuba/sessions.py b/src/sentry/snuba/sessions.py index 9ad504b51817f9..a326af38a31e52 100644 --- a/src/sentry/snuba/sessions.py +++ b/src/sentry/snuba/sessions.py @@ -50,7 +50,7 @@ def _get_changed_project_release_model_adoptions(project_ids): return rv -def get_oldest_health_data_for_releases(project_releases): +def _get_oldest_health_data_for_releases(project_releases): """Returns the oldest health data we have observed in a release in 90 days. This is used for backfilling. """ diff --git a/tests/snuba/sessions/test_sessions.py b/tests/snuba/sessions/test_sessions.py index 737316aeb611a2..c7048106ec3a20 100644 --- a/tests/snuba/sessions/test_sessions.py +++ b/tests/snuba/sessions/test_sessions.py @@ -9,7 +9,6 @@ from sentry.release_health.sessions import SessionsReleaseHealthBackend from sentry.snuba.sessions import ( _make_stats, - get_oldest_health_data_for_releases, get_project_releases_by_stability, get_project_releases_count, get_release_health_data_overview, @@ -135,7 +134,9 @@ def setUp(self): ) def test_get_oldest_health_data_for_releases(self): - data = get_oldest_health_data_for_releases([(self.project.id, self.session_release)]) + data = self.backend.get_oldest_health_data_for_releases( + [(self.project.id, self.session_release)] + ) assert data == { (self.project.id, self.session_release): format_timestamp( self.session_started // 3600 * 3600