diff --git a/src/sentry/api/endpoints/project_backfill_similar_issues_embeddings_records.py b/src/sentry/api/endpoints/project_backfill_similar_issues_embeddings_records.py new file mode 100644 index 00000000000000..7a7089f0afff09 --- /dev/null +++ b/src/sentry/api/endpoints/project_backfill_similar_issues_embeddings_records.py @@ -0,0 +1,31 @@ +from rest_framework.request import Request +from rest_framework.response import Response + +from sentry import features +from sentry.api.api_owners import ApiOwner +from sentry.api.api_publish_status import ApiPublishStatus +from sentry.api.base import region_silo_endpoint +from sentry.api.bases.project import ProjectEndpoint +from sentry.auth.superuser import is_active_superuser +from sentry.tasks.backfill_seer_grouping_records import backfill_seer_grouping_records + + +@region_silo_endpoint +class ProjectBackfillSimilarIssuesEmbeddingsRecords(ProjectEndpoint): + owner = ApiOwner.ISSUES + publish_status = { + "POST": ApiPublishStatus.PRIVATE, + } + + def post(self, request: Request, project) -> Response: + if not features.has( + "projects:similarity-embeddings-backfill", project + ) or not is_active_superuser(request): + return Response(status=404) + + last_processed_id = None + if request.data.get("last_processed_id"): + last_processed_id = int(request.data["last_processed_id"]) + + backfill_seer_grouping_records.delay(project.id, last_processed_id) + return Response(status=204) diff --git a/src/sentry/api/urls.py b/src/sentry/api/urls.py index 12e4ab7de5e507..25191a730a8b7a 100644 --- a/src/sentry/api/urls.py +++ b/src/sentry/api/urls.py @@ -28,6 +28,9 @@ OrganizationUnsubscribeIssue, OrganizationUnsubscribeProject, ) +from sentry.api.endpoints.project_backfill_similar_issues_embeddings_records import ( + ProjectBackfillSimilarIssuesEmbeddingsRecords, +) from sentry.api.endpoints.project_stacktrace_coverage import ProjectStacktraceCoverageEndpoint from sentry.api.endpoints.project_statistical_detectors import ProjectStatisticalDetectors from sentry.api.endpoints.release_thresholds.release_threshold import ReleaseThresholdEndpoint @@ -2541,6 +2544,11 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]: ProjectRuleTaskDetailsEndpoint.as_view(), name="sentry-api-0-project-rule-task-details", ), + re_path( + r"^(?P[^\/]+)/(?P[^\/]+)/backfill-similar-embeddings-records/$", + ProjectBackfillSimilarIssuesEmbeddingsRecords.as_view(), + name="sentry-api-0-project-backfill-similar-embeddings-records", + ), re_path( r"^(?P[^\/]+)/(?P[^\/]+)/stats/$", ProjectStatsEndpoint.as_view(), diff --git a/tests/sentry/api/endpoints/test_project_backfill_similar_issues_embeddings_records.py b/tests/sentry/api/endpoints/test_project_backfill_similar_issues_embeddings_records.py new file mode 100644 index 00000000000000..6be9d05a735db4 --- /dev/null +++ b/tests/sentry/api/endpoints/test_project_backfill_similar_issues_embeddings_records.py @@ -0,0 +1,63 @@ +from unittest.mock import patch + +from django.urls import reverse + +from sentry.testutils.cases import APITestCase +from sentry.testutils.helpers.features import with_feature + + +class ProjectBackfillSimilarIssuesEmbeddingsRecordsTest(APITestCase): + def setUp(self): + super().setUp() + self.login_as(self.user) + self.org = self.create_organization(owner=self.user) + self.project = self.create_project(organization=self.org) + self.url = reverse( + "sentry-api-0-project-backfill-similar-embeddings-records", + kwargs={ + "organization_slug": self.project.organization.slug, + "project_slug": self.project.slug, + }, + ) + + def test_post_no_feature_flag(self): + response = self.client.post(self.url, data={}) + assert response.status_code == 404, response.content + + @patch( + "sentry.api.endpoints.project_backfill_similar_issues_embeddings_records.is_active_superuser", + return_value=False, + ) + def test_post_not_superuser(self, mock_is_active_superuser): + response = self.client.post(self.url, data={}) + assert response.status_code == 404, response.content + + @patch( + "sentry.api.endpoints.project_backfill_similar_issues_embeddings_records.is_active_superuser", + return_value=True, + ) + @patch( + "sentry.api.endpoints.project_backfill_similar_issues_embeddings_records.backfill_seer_grouping_records.delay" + ) + @with_feature("projects:similarity-embeddings-backfill") + def test_post_success_no_last_processed_id( + self, mock_backfill_seer_grouping_records, mock_is_active_superuser + ): + response = self.client.post(self.url, data={}) + assert response.status_code == 204, response.content + mock_backfill_seer_grouping_records.assert_called_with(self.project.id, None) + + @patch( + "sentry.api.endpoints.project_backfill_similar_issues_embeddings_records.is_active_superuser", + return_value=True, + ) + @patch( + "sentry.api.endpoints.project_backfill_similar_issues_embeddings_records.backfill_seer_grouping_records.delay" + ) + @with_feature("projects:similarity-embeddings-backfill") + def test_post_success_last_processed_id( + self, mock_backfill_seer_grouping_records, mock_is_active_superuser + ): + response = self.client.post(self.url, data={"last_processed_id": "8"}) + assert response.status_code == 204, response.content + mock_backfill_seer_grouping_records.assert_called_with(self.project.id, 8)