diff --git a/src/sentry/integrations/api/endpoints/organization_integration_repos.py b/src/sentry/integrations/api/endpoints/organization_integration_repos.py index a633deca9c809f..0053c495313de9 100644 --- a/src/sentry/integrations/api/endpoints/organization_integration_repos.py +++ b/src/sentry/integrations/api/endpoints/organization_integration_repos.py @@ -22,6 +22,7 @@ class IntegrationRepository(TypedDict): identifier: str isInstalled: bool defaultBranch: str | None + externalId: str @cell_silo_endpoint @@ -85,6 +86,7 @@ def get( identifier=repo["identifier"], defaultBranch=repo.get("default_branch"), isInstalled=repo["identifier"] in installed_repo_names, + externalId=repo["external_id"], ) for repo in repositories if not installable_only or repo["identifier"] not in installed_repo_names diff --git a/tests/acceptance/test_scm_onboarding.py b/tests/acceptance/test_scm_onboarding.py index 0f872712336f2d..3cff96bb92db7e 100644 --- a/tests/acceptance/test_scm_onboarding.py +++ b/tests/acceptance/test_scm_onboarding.py @@ -52,6 +52,7 @@ def test_scm_onboarding_happy_path(self) -> None: "name": "sentry", "identifier": "getsentry/sentry", "default_branch": "master", + "external_id": "12345", }, ] @@ -162,6 +163,7 @@ def test_scm_onboarding_with_integration_install(self) -> None: "name": "sentry", "identifier": "getsentry/sentry", "default_branch": "master", + "external_id": "12345", }, ] @@ -273,6 +275,7 @@ def test_scm_onboarding_detection_error_falls_back_to_manual_picker(self) -> Non "name": "sentry", "identifier": "getsentry/sentry", "default_branch": "master", + "external_id": "12345", }, ] diff --git a/tests/sentry/integrations/api/endpoints/test_organization_integration_repos.py b/tests/sentry/integrations/api/endpoints/test_organization_integration_repos.py index 53a2d06b5694c4..e7caacc02cf588 100644 --- a/tests/sentry/integrations/api/endpoints/test_organization_integration_repos.py +++ b/tests/sentry/integrations/api/endpoints/test_organization_integration_repos.py @@ -22,8 +22,13 @@ def setUp(self) -> None: ) def test_simple(self, get_repositories: MagicMock) -> None: get_repositories.return_value = [ - {"name": "rad-repo", "identifier": "Example/rad-repo", "default_branch": "main"}, - {"name": "cool-repo", "identifier": "Example/cool-repo"}, + { + "name": "rad-repo", + "identifier": "Example/rad-repo", + "default_branch": "main", + "external_id": "rad-repo", + }, + {"name": "cool-repo", "identifier": "Example/cool-repo", "external_id": "cool-repo"}, ] response = self.client.get(self.path, format="json") @@ -35,12 +40,14 @@ def test_simple(self, get_repositories: MagicMock) -> None: "identifier": "Example/rad-repo", "defaultBranch": "main", "isInstalled": False, + "externalId": "rad-repo", }, { "name": "cool-repo", "identifier": "Example/cool-repo", "defaultBranch": None, "isInstalled": False, + "externalId": "cool-repo", }, ], "searchable": True, @@ -55,8 +62,9 @@ def test_hide_hidden_repos(self, get_repositories: MagicMock) -> None: "name": "rad-repo", "identifier": "Example/rad-repo", "default_branch": "main", + "external_id": "rad-repo", }, - {"name": "cool-repo", "identifier": "Example/cool-repo"}, + {"name": "cool-repo", "identifier": "Example/cool-repo", "external_id": "cool-repo"}, ] self.create_repo( @@ -75,6 +83,7 @@ def test_hide_hidden_repos(self, get_repositories: MagicMock) -> None: "identifier": "Example/cool-repo", "defaultBranch": None, "isInstalled": False, + "externalId": "cool-repo", }, ], "searchable": True, @@ -85,9 +94,23 @@ def test_hide_hidden_repos(self, get_repositories: MagicMock) -> None: ) def test_installable_only(self, get_repositories: MagicMock) -> None: get_repositories.return_value = [ - {"name": "rad-repo", "identifier": "Example/rad-repo", "default_branch": "main"}, - {"name": "cool-repo", "identifier": "Example/cool-repo", "default_branch": "dev"}, - {"name": "awesome-repo", "identifier": "Example/awesome-repo"}, + { + "name": "rad-repo", + "identifier": "Example/rad-repo", + "default_branch": "main", + "external_id": "rad-repo", + }, + { + "name": "cool-repo", + "identifier": "Example/cool-repo", + "default_branch": "dev", + "external_id": "cool-repo", + }, + { + "name": "awesome-repo", + "identifier": "Example/awesome-repo", + "external_id": "awesome-repo", + }, ] self.create_repo( @@ -105,12 +128,14 @@ def test_installable_only(self, get_repositories: MagicMock) -> None: "identifier": "Example/cool-repo", "defaultBranch": "dev", "isInstalled": False, + "externalId": "cool-repo", }, { "name": "awesome-repo", "identifier": "Example/awesome-repo", "defaultBranch": None, "isInstalled": False, + "externalId": "awesome-repo", }, ], "searchable": True, @@ -121,8 +146,18 @@ def test_installable_only(self, get_repositories: MagicMock) -> None: ) def test_is_installed_field(self, get_repositories: MagicMock) -> None: get_repositories.return_value = [ - {"name": "rad-repo", "identifier": "Example/rad-repo", "default_branch": "main"}, - {"name": "rad-repo", "identifier": "Example2/rad-repo", "default_branch": "dev"}, + { + "name": "rad-repo", + "identifier": "Example/rad-repo", + "default_branch": "main", + "external_id": "rad-repo", + }, + { + "name": "rad-repo", + "identifier": "Example2/rad-repo", + "default_branch": "dev", + "external_id": "rad-repo", + }, ] self.create_repo( @@ -141,11 +176,13 @@ def test_is_installed_field(self, get_repositories: MagicMock) -> None: "identifier": "Example/rad-repo", "defaultBranch": "main", "isInstalled": True, + "externalId": "rad-repo", }, { "name": "rad-repo", "identifier": "Example2/rad-repo", "defaultBranch": "dev", + "externalId": "rad-repo", "isInstalled": False, }, ], @@ -161,7 +198,12 @@ def test_repo_installed_by_other_org_not_excluded(self, get_repositories: MagicM one organization should not affect the available repos for the other. """ get_repositories.return_value = [ - {"name": "shared-repo", "identifier": "Example/shared-repo", "default_branch": "main"}, + { + "name": "shared-repo", + "identifier": "Example/shared-repo", + "default_branch": "main", + "external_id": "shared-repo", + }, ] other_org = self.create_organization(owner=self.user, name="other-org") @@ -182,6 +224,7 @@ def test_repo_installed_by_other_org_not_excluded(self, get_repositories: MagicM "identifier": "Example/shared-repo", "defaultBranch": "main", "isInstalled": False, + "externalId": "shared-repo", }, ], "searchable": True, @@ -193,7 +236,12 @@ def test_repo_installed_by_other_org_not_excluded(self, get_repositories: MagicM def test_accessible_only_passes_param(self, get_repositories: MagicMock) -> None: """When accessibleOnly=true, passes accessible_only to get_repositories.""" get_repositories.return_value = [ - {"name": "rad-repo", "identifier": "Example/rad-repo", "default_branch": "main"}, + { + "name": "rad-repo", + "identifier": "Example/rad-repo", + "default_branch": "main", + "external_id": "rad-repo", + }, ] response = self.client.get( self.path, format="json", data={"search": "rad", "accessibleOnly": "true"} @@ -208,6 +256,7 @@ def test_accessible_only_passes_param(self, get_repositories: MagicMock) -> None "identifier": "Example/rad-repo", "defaultBranch": "main", "isInstalled": False, + "externalId": "rad-repo", }, ], "searchable": True, @@ -219,7 +268,12 @@ def test_accessible_only_passes_param(self, get_repositories: MagicMock) -> None def test_accessible_only_without_search(self, get_repositories: MagicMock) -> None: """When accessibleOnly=true but no search, passes both params through.""" get_repositories.return_value = [ - {"name": "rad-repo", "identifier": "Example/rad-repo", "default_branch": "main"}, + { + "name": "rad-repo", + "identifier": "Example/rad-repo", + "default_branch": "main", + "external_id": "rad-repo", + }, ] response = self.client.get(self.path, format="json", data={"accessibleOnly": "true"}) @@ -232,8 +286,18 @@ def test_accessible_only_without_search(self, get_repositories: MagicMock) -> No def test_accessible_only_with_installable_only(self, get_repositories: MagicMock) -> None: """Both filters compose: accessible scopes the fetch, installable excludes installed repos.""" get_repositories.return_value = [ - {"name": "rad-repo", "identifier": "Example/rad-repo", "default_branch": "main"}, - {"name": "cool-repo", "identifier": "Example/cool-repo", "default_branch": "dev"}, + { + "name": "rad-repo", + "identifier": "Example/rad-repo", + "default_branch": "main", + "external_id": "rad-repo", + }, + { + "name": "cool-repo", + "identifier": "Example/cool-repo", + "default_branch": "dev", + "external_id": "cool-repo", + }, ] self.create_repo( @@ -257,6 +321,7 @@ def test_accessible_only_with_installable_only(self, get_repositories: MagicMock "identifier": "Example/cool-repo", "defaultBranch": "dev", "isInstalled": False, + "externalId": "cool-repo", }, ], "searchable": True,