From ef687035e008b1cfe1800d2ad574e97ecde50212 Mon Sep 17 00:00:00 2001 From: Ajay Singh Date: Wed, 3 Dec 2025 17:29:56 -0800 Subject: [PATCH] init webhook guard logic with metric --- src/sentry/integrations/github/utils.py | 38 ++++++++- src/sentry/integrations/github/webhook.py | 12 +++ .../sentry/integrations/github/test_utils.py | 83 ++++++++++++++++++- 3 files changed, 131 insertions(+), 2 deletions(-) diff --git a/src/sentry/integrations/github/utils.py b/src/sentry/integrations/github/utils.py index 7dba5d87db8a9c..91568e94b70cbe 100644 --- a/src/sentry/integrations/github/utils.py +++ b/src/sentry/integrations/github/utils.py @@ -8,7 +8,12 @@ from rest_framework.response import Response -from sentry import options +from sentry import features, options +from sentry.constants import ENABLE_PR_REVIEW_TEST_GENERATION_DEFAULT, HIDE_AI_FEATURES_DEFAULT +from sentry.integrations.models.repository_project_path_config import RepositoryProjectPathConfig +from sentry.models.organization import Organization +from sentry.models.repository import Repository +from sentry.seer.seer_setup import get_seer_org_acknowledgement from sentry.utils import jwt logger = logging.getLogger(__name__) @@ -78,3 +83,34 @@ def parse_github_blob_url(repo_url: str, source_url: str) -> tuple[str, str]: branch, _, remainder = after_blob.partition("/") return branch, remainder.lstrip("/") + + +def has_seer_and_ai_features_enabled_for_repo(organization: Organization, repo: Repository) -> bool: + """ + Check if Seer is enabled and AI features are configured for a repository. + + Returns: + True if Seer and AI features are enabled for the repository else False + """ + + seer_enabled = get_seer_org_acknowledgement(organization) + if not seer_enabled: + return False + + repo_enabled = RepositoryProjectPathConfig.objects.filter( + repository=repo, + organization_id=organization.id, + ).exists() + if not repo_enabled: + return False + + gen_ai_features = features.has( + "organizations:gen-ai-features", organization + ) and not organization.get_option("sentry:hide_ai_features", HIDE_AI_FEATURES_DEFAULT) + + ai_code_review_enabled = organization.get_option( + "sentry:enable_pr_review_test_generation", + ENABLE_PR_REVIEW_TEST_GENERATION_DEFAULT, + ) + + return gen_ai_features or ai_code_review_enabled diff --git a/src/sentry/integrations/github/webhook.py b/src/sentry/integrations/github/webhook.py index 4fa05469a6e0a9..1a0362c0c12d12 100644 --- a/src/sentry/integrations/github/webhook.py +++ b/src/sentry/integrations/github/webhook.py @@ -54,6 +54,7 @@ from .repository import GitHubRepositoryProvider from .tasks.codecov_account_unlink import codecov_account_unlink from .types import IssueEvenntWebhookActionType +from .utils import has_seer_and_ai_features_enabled_for_repo logger = logging.getLogger("sentry.webhooks") @@ -767,6 +768,17 @@ def _handle( ) if created: + # Track AI contributor if eligible + if has_seer_and_ai_features_enabled_for_repo(organization, repo): + metrics.incr( + "github.webhook.organization_contributor.should_create", + sample_rate=1.0, + tags={ + "organization_id": organization.id, + "repository_id": repo.id, + }, + ) + metrics.incr( "github.webhook.pull_request.created", sample_rate=1.0, diff --git a/tests/sentry/integrations/github/test_utils.py b/tests/sentry/integrations/github/test_utils.py index 8ddc2be3527951..fa0e374f9c1e44 100644 --- a/tests/sentry/integrations/github/test_utils.py +++ b/tests/sentry/integrations/github/test_utils.py @@ -1,6 +1,87 @@ +from unittest.mock import patch + import pytest -from sentry.integrations.github.utils import parse_github_blob_url +from sentry.integrations.github.utils import ( + has_seer_and_ai_features_enabled_for_repo, + parse_github_blob_url, +) +from sentry.testutils.cases import TestCase + + +class HasSeerAndAiFeaturesEnabledForRepoTest(TestCase): + def setUp(self): + super().setUp() + self.integration = self.create_integration( + organization=self.organization, + provider="github", + external_id="github:1", + ) + self.repo = self.create_repo( + project=self.project, + provider="integrations:github", + integration_id=self.integration.id, + ) + + @patch( + "sentry.integrations.github.utils.get_seer_org_acknowledgement", + return_value=False, + ) + def test_returns_false_when_seer_not_acknowledged(self, mock_seer_ack): + result = has_seer_and_ai_features_enabled_for_repo(self.organization, self.repo) + assert result is False + + @patch( + "sentry.integrations.github.utils.get_seer_org_acknowledgement", + return_value=True, + ) + def test_returns_false_when_no_code_mappings(self, mock_seer_ack): + result = has_seer_and_ai_features_enabled_for_repo(self.organization, self.repo) + assert result is False + + @patch( + "sentry.integrations.github.utils.get_seer_org_acknowledgement", + return_value=True, + ) + def test_returns_false_when_ai_features_disabled(self, mock_seer_ack): + self.create_code_mapping(project=self.project, repo=self.repo) + + result = has_seer_and_ai_features_enabled_for_repo(self.organization, self.repo) + assert result is False + + @patch( + "sentry.integrations.github.utils.get_seer_org_acknowledgement", + return_value=True, + ) + def test_returns_true_when_gen_ai_features_enabled(self, mock_seer_ack): + self.create_code_mapping(project=self.project, repo=self.repo) + + with self.feature("organizations:gen-ai-features"): + result = has_seer_and_ai_features_enabled_for_repo(self.organization, self.repo) + assert result is True + + @patch( + "sentry.integrations.github.utils.get_seer_org_acknowledgement", + return_value=True, + ) + def test_returns_false_when_gen_ai_features_enabled_but_hidden(self, mock_seer_ack): + self.create_code_mapping(project=self.project, repo=self.repo) + self.organization.update_option("sentry:hide_ai_features", True) + + with self.feature("organizations:gen-ai-features"): + result = has_seer_and_ai_features_enabled_for_repo(self.organization, self.repo) + assert result is False + + @patch( + "sentry.integrations.github.utils.get_seer_org_acknowledgement", + return_value=True, + ) + def test_returns_true_when_ai_code_review_enabled(self, mock_seer_ack): + self.create_code_mapping(project=self.project, repo=self.repo) + self.organization.update_option("sentry:enable_pr_review_test_generation", True) + + result = has_seer_and_ai_features_enabled_for_repo(self.organization, self.repo) + assert result is True @pytest.mark.parametrize(