Skip to content

Commit ee244d0

Browse files
authored
fix(bug-prediction): Require code mappings in initial org ids fetch (#103030)
in the near future, sounds like we can get rid of this RPC and have overwatch supply it. for now, we should make this line up w/ the [constraints that the actual tools have](https://github.com/getsentry/sentry/blob/d1e24794df851ed5e93d298c564ae18b76d04d41/src/sentry/seer/fetch_issues/utils.py#L86)
1 parent fa3d768 commit ee244d0

File tree

2 files changed

+202
-42
lines changed

2 files changed

+202
-42
lines changed

src/sentry/seer/endpoints/seer_rpc.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
from sentry.hybridcloud.rpc.service import RpcAuthenticationSetupException, RpcResolutionException
5858
from sentry.hybridcloud.rpc.sig import SerializableFunctionValueException
5959
from sentry.integrations.github_enterprise.integration import GitHubEnterpriseIntegration
60+
from sentry.integrations.models.repository_project_path_config import RepositoryProjectPathConfig
6061
from sentry.integrations.services.integration import integration_service
6162
from sentry.integrations.types import IntegrationProviderSlug
6263
from sentry.models.organization import Organization, OrganizationStatus
@@ -278,27 +279,48 @@ def _can_use_prevent_ai_features(org: Organization) -> bool:
278279
return not hide_ai_features and pr_review_test_generation_enabled
279280

280281

282+
class SentryOrganizaionIdsAndSlugs(TypedDict):
283+
org_ids: list[int]
284+
org_slugs: list[str]
285+
286+
281287
def get_sentry_organization_ids(
282-
*, full_repo_name: str, external_id: str, provider: str = "integrations:github"
283-
) -> dict:
288+
*, external_id: str, provider: str = "integrations:github", **kwargs
289+
) -> SentryOrganizaionIdsAndSlugs:
284290
"""
285291
Get the Sentry organization ID for a given Repository.
286292
287293
Args:
288-
full_repo_name: The full name of the repository (e.g. "getsentry/sentry")
289294
external_id: The id of the repo in the provider's system
290295
provider: The provider of the repository (e.g. "integrations:github")
291296
"""
292297

293298
# It's possible that multiple orgs will be returned for a given repo.
294-
organization_ids = Repository.objects.filter(
295-
name=full_repo_name, provider=provider, status=ObjectStatus.ACTIVE, external_id=external_id
296-
).values_list("organization_id", flat=True)
299+
repositories = Repository.objects.filter(
300+
provider=provider,
301+
external_id=external_id,
302+
status=ObjectStatus.ACTIVE,
303+
)
304+
repo_ids = repositories.values_list("id", flat=True)
305+
306+
# Filter to only repositories that have code mappings.
307+
repo_ids_with_config = (
308+
RepositoryProjectPathConfig.objects.filter(repository_id__in=repo_ids)
309+
.values_list("repository_id", flat=True)
310+
.distinct()
311+
)
312+
313+
organization_ids = repositories.filter(id__in=repo_ids_with_config).values_list(
314+
"organization_id", flat=True
315+
)
297316
organizations = Organization.objects.filter(id__in=organization_ids)
298317
# We then filter out all orgs that didn't give us consent to use AI features.
299318
orgs_with_consent = [org for org in organizations if _can_use_prevent_ai_features(org)]
300319

301-
return {"org_ids": [organization.id for organization in orgs_with_consent]}
320+
return {
321+
"org_ids": [organization.id for organization in orgs_with_consent],
322+
"org_slugs": [organization.slug for organization in orgs_with_consent],
323+
}
302324

303325

304326
def get_organization_autofix_consent(*, org_id: int) -> dict:

tests/sentry/seer/endpoints/test_seer_rpc.py

Lines changed: 173 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from sentry.constants import ObjectStatus
1515
from sentry.integrations.models.integration import Integration
16+
from sentry.integrations.models.repository_project_path_config import RepositoryProjectPathConfig
1617
from sentry.models.options.organization_option import OrganizationOption
1718
from sentry.models.repository import Repository
1819
from sentry.seer.endpoints.seer_rpc import (
@@ -505,37 +506,57 @@ def test_get_github_enterprise_integration_config_invalid_encrypt_key(
505506
def test_get_sentry_organization_ids_repository_found(self) -> None:
506507
"""Test when repository exists and is active"""
507508

509+
# Create a project
510+
project = self.create_project(organization=self.organization)
511+
512+
# Create an integration
513+
integration = self.create_integration(
514+
organization=self.organization,
515+
provider="github",
516+
external_id="github:1",
517+
)
518+
org_integration = integration.organizationintegration_set.first()
519+
assert org_integration is not None
520+
508521
# Create a repository
509-
Repository.objects.create(
522+
repo = Repository.objects.create(
510523
name="getsentry/sentry",
511524
organization_id=self.organization.id,
512525
provider="integrations:github",
513526
external_id="1234567890",
514527
status=ObjectStatus.ACTIVE,
515528
)
516529

517-
# By default the organization has pr_review turned off
518-
result = get_sentry_organization_ids(
519-
full_repo_name="getsentry/sentry", external_id="1234567890"
530+
# Create a RepositoryProjectPathConfig
531+
RepositoryProjectPathConfig.objects.create(
532+
repository=repo,
533+
project=project,
534+
organization_integration_id=org_integration.id,
535+
integration_id=org_integration.integration_id,
536+
organization_id=self.organization.id,
537+
stack_root="/",
538+
source_root="/",
520539
)
521-
assert result == {"org_ids": []}
540+
541+
# By default the organization has pr_review turned off
542+
result = get_sentry_organization_ids(external_id="1234567890")
543+
assert result == {"org_ids": [], "org_slugs": []}
522544

523545
# Turn on pr_review
524546
OrganizationOption.objects.set_value(
525547
self.organization, "sentry:enable_pr_review_test_generation", True
526548
)
527-
result = get_sentry_organization_ids(
528-
full_repo_name="getsentry/sentry", external_id="1234567890"
529-
)
530-
assert result == {"org_ids": [self.organization.id]}
549+
result = get_sentry_organization_ids(external_id="1234567890")
550+
assert result == {
551+
"org_ids": [self.organization.id],
552+
"org_slugs": [self.organization.slug],
553+
}
531554

532555
def test_get_sentry_organization_ids_repository_not_found(self) -> None:
533556
"""Test when repository does not exist"""
534-
result = get_sentry_organization_ids(
535-
full_repo_name="nonexistent/repo", external_id="1234567890"
536-
)
557+
result = get_sentry_organization_ids(external_id="1234567890")
537558

538-
assert result == {"org_ids": []}
559+
assert result == {"org_ids": [], "org_slugs": []}
539560

540561
def test_get_sentry_organization_ids_repository_inactive(self) -> None:
541562
"""Test when repository exists but is not active"""
@@ -549,12 +570,10 @@ def test_get_sentry_organization_ids_repository_inactive(self) -> None:
549570
status=ObjectStatus.DISABLED,
550571
)
551572

552-
result = get_sentry_organization_ids(
553-
full_repo_name="getsentry/sentry", external_id="1234567890"
554-
)
573+
result = get_sentry_organization_ids(external_id="1234567890")
555574

556575
# Should not find the repository because it's not active
557-
assert result == {"org_ids": []}
576+
assert result == {"org_ids": [], "org_slugs": []}
558577

559578
def test_get_sentry_organization_ids_different_provider(self) -> None:
560579
"""Test when repository exists but with different provider"""
@@ -569,52 +588,92 @@ def test_get_sentry_organization_ids_different_provider(self) -> None:
569588
)
570589

571590
# Search with default provider (integrations:github)
572-
result = get_sentry_organization_ids(
573-
full_repo_name="getsentry/sentry", external_id="1234567890"
574-
)
591+
result = get_sentry_organization_ids(external_id="1234567890")
575592

576593
# Should not find the repository because provider doesn't match
577-
assert result == {"org_ids": []}
594+
assert result == {"org_ids": [], "org_slugs": []}
578595

579596
def test_get_sentry_organization_ids_multiple_repos_same_name_different_providers(self) -> None:
580597
"""Test when multiple repositories exist with same name but different providers"""
581598
org2 = self.create_organization(owner=self.user)
582599

600+
# Create projects
601+
project1 = self.create_project(organization=self.organization)
602+
project2 = self.create_project(organization=org2)
603+
604+
# Create integrations
605+
integration1 = self.create_integration(
606+
organization=self.organization,
607+
provider="github",
608+
external_id="github:1",
609+
)
610+
org_integration1 = integration1.organizationintegration_set.first()
611+
assert org_integration1 is not None
612+
613+
integration2 = self.create_integration(
614+
organization=org2,
615+
provider="gitlab",
616+
external_id="gitlab:1",
617+
)
618+
org_integration2 = integration2.organizationintegration_set.first()
619+
assert org_integration2 is not None
620+
583621
# Create repositories with same name but different providers
584-
Repository.objects.create(
622+
repo1 = Repository.objects.create(
585623
name="getsentry/sentry",
586624
organization_id=self.organization.id,
587625
provider="integrations:github",
588626
external_id="1234567890",
589627
status=ObjectStatus.ACTIVE,
590628
)
591-
Repository.objects.create(
629+
repo2 = Repository.objects.create(
592630
name="getsentry/sentry",
593631
organization_id=org2.id,
594632
provider="integrations:gitlab",
595633
external_id="1234567890",
596634
status=ObjectStatus.ACTIVE,
597635
)
636+
637+
# Create RepositoryProjectPathConfigs
638+
RepositoryProjectPathConfig.objects.create(
639+
repository=repo1,
640+
project=project1,
641+
organization_integration_id=org_integration1.id,
642+
integration_id=org_integration1.integration_id,
643+
organization_id=self.organization.id,
644+
stack_root="/",
645+
source_root="/",
646+
)
647+
RepositoryProjectPathConfig.objects.create(
648+
repository=repo2,
649+
project=project2,
650+
organization_integration_id=org_integration2.id,
651+
integration_id=org_integration2.integration_id,
652+
organization_id=org2.id,
653+
stack_root="/",
654+
source_root="/",
655+
)
656+
598657
OrganizationOption.objects.set_value(
599658
self.organization, "sentry:enable_pr_review_test_generation", True
600659
)
601660
OrganizationOption.objects.set_value(org2, "sentry:enable_pr_review_test_generation", True)
602661

603662
# Search for GitHub provider
604-
result = get_sentry_organization_ids(
605-
full_repo_name="getsentry/sentry", external_id="1234567890"
606-
)
663+
result = get_sentry_organization_ids(external_id="1234567890")
607664

608-
assert result == {"org_ids": [self.organization.id]}
665+
assert result == {
666+
"org_ids": [self.organization.id],
667+
"org_slugs": [self.organization.slug],
668+
}
609669

610670
# Search for GitLab provider
611671
result = get_sentry_organization_ids(
612-
full_repo_name="getsentry/sentry",
613672
provider="integrations:gitlab",
614673
external_id="1234567890",
615674
)
616675

617-
assert result == {"org_ids": [org2.id]}
676+
assert result == {"org_ids": [org2.id], "org_slugs": [org2.slug]}
618677

619678
def test_get_sentry_organization_ids_multiple_orgs_same_repo(self) -> None:
620679
"""Test when multiple repositories exist with same name but different providers and provider is provided"""
@@ -629,8 +688,38 @@ def test_get_sentry_organization_ids_multiple_orgs_same_repo(self) -> None:
629688
OrganizationOption.objects.set_value(org2, "sentry:enable_pr_review_test_generation", True)
630689
OrganizationOption.objects.set_value(org3, "sentry:enable_pr_review_test_generation", True)
631690

691+
# Create projects
692+
project1 = self.create_project(organization=self.organization)
693+
project2 = self.create_project(organization=org2)
694+
project3 = self.create_project(organization=org3)
695+
696+
# Create integrations
697+
integration1 = self.create_integration(
698+
organization=self.organization,
699+
provider="github",
700+
external_id="github:1",
701+
)
702+
org_integration1 = integration1.organizationintegration_set.first()
703+
assert org_integration1 is not None
704+
705+
integration2 = self.create_integration(
706+
organization=org2,
707+
provider="github",
708+
external_id="github:2",
709+
)
710+
org_integration2 = integration2.organizationintegration_set.first()
711+
assert org_integration2 is not None
712+
713+
integration3 = self.create_integration(
714+
organization=org3,
715+
provider="github",
716+
external_id="github:3",
717+
)
718+
org_integration3 = integration3.organizationintegration_set.first()
719+
assert org_integration3 is not None
720+
632721
# repo in org 1
633-
Repository.objects.create(
722+
repo1 = Repository.objects.create(
634723
name="getsentry/sentry",
635724
organization_id=self.organization.id,
636725
provider="integrations:github",
@@ -639,7 +728,7 @@ def test_get_sentry_organization_ids_multiple_orgs_same_repo(self) -> None:
639728
)
640729

641730
# repo in org 2
642-
Repository.objects.create(
731+
repo2 = Repository.objects.create(
643732
name="getsentry/sentry",
644733
organization_id=org2.id,
645734
provider="integrations:github",
@@ -648,20 +737,69 @@ def test_get_sentry_organization_ids_multiple_orgs_same_repo(self) -> None:
648737
)
649738

650739
# repo in org 3
651-
Repository.objects.create(
740+
repo3 = Repository.objects.create(
652741
name="getsentry/sentry",
653742
organization_id=org3.id,
654743
provider="integrations:github",
655744
external_id="1234567890",
656745
status=ObjectStatus.ACTIVE,
657746
)
658747

748+
# Create RepositoryProjectPathConfigs
749+
RepositoryProjectPathConfig.objects.create(
750+
repository=repo1,
751+
project=project1,
752+
organization_integration_id=org_integration1.id,
753+
integration_id=org_integration1.integration_id,
754+
organization_id=self.organization.id,
755+
stack_root="/",
756+
source_root="/",
757+
)
758+
RepositoryProjectPathConfig.objects.create(
759+
repository=repo2,
760+
project=project2,
761+
organization_integration_id=org_integration2.id,
762+
integration_id=org_integration2.integration_id,
763+
organization_id=org2.id,
764+
stack_root="/",
765+
source_root="/",
766+
)
767+
RepositoryProjectPathConfig.objects.create(
768+
repository=repo3,
769+
project=project3,
770+
organization_integration_id=org_integration3.id,
771+
integration_id=org_integration3.integration_id,
772+
organization_id=org3.id,
773+
stack_root="/",
774+
source_root="/",
775+
)
776+
659777
# Search for GitHub provider
660-
result = get_sentry_organization_ids(
661-
full_repo_name="getsentry/sentry", external_id="1234567890"
778+
result = get_sentry_organization_ids(external_id="1234567890")
779+
780+
assert set(result["org_ids"]) == {self.organization.id, org2.id}
781+
assert set(result["org_slugs"]) == {self.organization.slug, org2.slug}
782+
783+
def test_get_sentry_organization_ids_no_repo_project_path_config(self) -> None:
784+
"""Test when repository exists but has no RepositoryProjectPathConfig"""
785+
# Create a repository without any RepositoryProjectPathConfig
786+
Repository.objects.create(
787+
name="getsentry/sentry",
788+
organization_id=self.organization.id,
789+
provider="integrations:github",
790+
external_id="1234567890",
791+
status=ObjectStatus.ACTIVE,
792+
)
793+
794+
# Turn on pr_review
795+
OrganizationOption.objects.set_value(
796+
self.organization, "sentry:enable_pr_review_test_generation", True
662797
)
663798

664-
assert result == {"org_ids": [self.organization.id, org2.id]}
799+
# Should not find the organization because there's no RepositoryProjectPathConfig
800+
result = get_sentry_organization_ids(external_id="1234567890")
801+
802+
assert result == {"org_ids": [], "org_slugs": []}
665803

666804
def test_send_seer_webhook_invalid_event_name(self) -> None:
667805
"""Test that send_seer_webhook returns error for invalid event names"""

0 commit comments

Comments
 (0)