Skip to content

Commit 275aa80

Browse files
authored
feat(integrations): Return externalId from OrganizationIntegrationReposEndpoint (#112529)
As requested in #112327 (comment) <!-- Describe your PR here. -->
1 parent 9c185a6 commit 275aa80

3 files changed

Lines changed: 83 additions & 13 deletions

File tree

‎src/sentry/integrations/api/endpoints/organization_integration_repos.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class IntegrationRepository(TypedDict):
2222
identifier: str
2323
isInstalled: bool
2424
defaultBranch: str | None
25+
externalId: str
2526

2627

2728
@cell_silo_endpoint
@@ -85,6 +86,7 @@ def get(
8586
identifier=repo["identifier"],
8687
defaultBranch=repo.get("default_branch"),
8788
isInstalled=repo["identifier"] in installed_repo_names,
89+
externalId=repo["external_id"],
8890
)
8991
for repo in repositories
9092
if not installable_only or repo["identifier"] not in installed_repo_names

‎tests/acceptance/test_scm_onboarding.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def test_scm_onboarding_happy_path(self) -> None:
5252
"name": "sentry",
5353
"identifier": "getsentry/sentry",
5454
"default_branch": "master",
55+
"external_id": "12345",
5556
},
5657
]
5758

@@ -162,6 +163,7 @@ def test_scm_onboarding_with_integration_install(self) -> None:
162163
"name": "sentry",
163164
"identifier": "getsentry/sentry",
164165
"default_branch": "master",
166+
"external_id": "12345",
165167
},
166168
]
167169

@@ -273,6 +275,7 @@ def test_scm_onboarding_detection_error_falls_back_to_manual_picker(self) -> Non
273275
"name": "sentry",
274276
"identifier": "getsentry/sentry",
275277
"default_branch": "master",
278+
"external_id": "12345",
276279
},
277280
]
278281

‎tests/sentry/integrations/api/endpoints/test_organization_integration_repos.py‎

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,13 @@ def setUp(self) -> None:
2222
)
2323
def test_simple(self, get_repositories: MagicMock) -> None:
2424
get_repositories.return_value = [
25-
{"name": "rad-repo", "identifier": "Example/rad-repo", "default_branch": "main"},
26-
{"name": "cool-repo", "identifier": "Example/cool-repo"},
25+
{
26+
"name": "rad-repo",
27+
"identifier": "Example/rad-repo",
28+
"default_branch": "main",
29+
"external_id": "rad-repo",
30+
},
31+
{"name": "cool-repo", "identifier": "Example/cool-repo", "external_id": "cool-repo"},
2732
]
2833
response = self.client.get(self.path, format="json")
2934

@@ -35,12 +40,14 @@ def test_simple(self, get_repositories: MagicMock) -> None:
3540
"identifier": "Example/rad-repo",
3641
"defaultBranch": "main",
3742
"isInstalled": False,
43+
"externalId": "rad-repo",
3844
},
3945
{
4046
"name": "cool-repo",
4147
"identifier": "Example/cool-repo",
4248
"defaultBranch": None,
4349
"isInstalled": False,
50+
"externalId": "cool-repo",
4451
},
4552
],
4653
"searchable": True,
@@ -55,8 +62,9 @@ def test_hide_hidden_repos(self, get_repositories: MagicMock) -> None:
5562
"name": "rad-repo",
5663
"identifier": "Example/rad-repo",
5764
"default_branch": "main",
65+
"external_id": "rad-repo",
5866
},
59-
{"name": "cool-repo", "identifier": "Example/cool-repo"},
67+
{"name": "cool-repo", "identifier": "Example/cool-repo", "external_id": "cool-repo"},
6068
]
6169

6270
self.create_repo(
@@ -75,6 +83,7 @@ def test_hide_hidden_repos(self, get_repositories: MagicMock) -> None:
7583
"identifier": "Example/cool-repo",
7684
"defaultBranch": None,
7785
"isInstalled": False,
86+
"externalId": "cool-repo",
7887
},
7988
],
8089
"searchable": True,
@@ -85,9 +94,23 @@ def test_hide_hidden_repos(self, get_repositories: MagicMock) -> None:
8594
)
8695
def test_installable_only(self, get_repositories: MagicMock) -> None:
8796
get_repositories.return_value = [
88-
{"name": "rad-repo", "identifier": "Example/rad-repo", "default_branch": "main"},
89-
{"name": "cool-repo", "identifier": "Example/cool-repo", "default_branch": "dev"},
90-
{"name": "awesome-repo", "identifier": "Example/awesome-repo"},
97+
{
98+
"name": "rad-repo",
99+
"identifier": "Example/rad-repo",
100+
"default_branch": "main",
101+
"external_id": "rad-repo",
102+
},
103+
{
104+
"name": "cool-repo",
105+
"identifier": "Example/cool-repo",
106+
"default_branch": "dev",
107+
"external_id": "cool-repo",
108+
},
109+
{
110+
"name": "awesome-repo",
111+
"identifier": "Example/awesome-repo",
112+
"external_id": "awesome-repo",
113+
},
91114
]
92115

93116
self.create_repo(
@@ -105,12 +128,14 @@ def test_installable_only(self, get_repositories: MagicMock) -> None:
105128
"identifier": "Example/cool-repo",
106129
"defaultBranch": "dev",
107130
"isInstalled": False,
131+
"externalId": "cool-repo",
108132
},
109133
{
110134
"name": "awesome-repo",
111135
"identifier": "Example/awesome-repo",
112136
"defaultBranch": None,
113137
"isInstalled": False,
138+
"externalId": "awesome-repo",
114139
},
115140
],
116141
"searchable": True,
@@ -121,8 +146,18 @@ def test_installable_only(self, get_repositories: MagicMock) -> None:
121146
)
122147
def test_is_installed_field(self, get_repositories: MagicMock) -> None:
123148
get_repositories.return_value = [
124-
{"name": "rad-repo", "identifier": "Example/rad-repo", "default_branch": "main"},
125-
{"name": "rad-repo", "identifier": "Example2/rad-repo", "default_branch": "dev"},
149+
{
150+
"name": "rad-repo",
151+
"identifier": "Example/rad-repo",
152+
"default_branch": "main",
153+
"external_id": "rad-repo",
154+
},
155+
{
156+
"name": "rad-repo",
157+
"identifier": "Example2/rad-repo",
158+
"default_branch": "dev",
159+
"external_id": "rad-repo",
160+
},
126161
]
127162

128163
self.create_repo(
@@ -141,11 +176,13 @@ def test_is_installed_field(self, get_repositories: MagicMock) -> None:
141176
"identifier": "Example/rad-repo",
142177
"defaultBranch": "main",
143178
"isInstalled": True,
179+
"externalId": "rad-repo",
144180
},
145181
{
146182
"name": "rad-repo",
147183
"identifier": "Example2/rad-repo",
148184
"defaultBranch": "dev",
185+
"externalId": "rad-repo",
149186
"isInstalled": False,
150187
},
151188
],
@@ -161,7 +198,12 @@ def test_repo_installed_by_other_org_not_excluded(self, get_repositories: MagicM
161198
one organization should not affect the available repos for the other.
162199
"""
163200
get_repositories.return_value = [
164-
{"name": "shared-repo", "identifier": "Example/shared-repo", "default_branch": "main"},
201+
{
202+
"name": "shared-repo",
203+
"identifier": "Example/shared-repo",
204+
"default_branch": "main",
205+
"external_id": "shared-repo",
206+
},
165207
]
166208

167209
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
182224
"identifier": "Example/shared-repo",
183225
"defaultBranch": "main",
184226
"isInstalled": False,
227+
"externalId": "shared-repo",
185228
},
186229
],
187230
"searchable": True,
@@ -193,7 +236,12 @@ def test_repo_installed_by_other_org_not_excluded(self, get_repositories: MagicM
193236
def test_accessible_only_passes_param(self, get_repositories: MagicMock) -> None:
194237
"""When accessibleOnly=true, passes accessible_only to get_repositories."""
195238
get_repositories.return_value = [
196-
{"name": "rad-repo", "identifier": "Example/rad-repo", "default_branch": "main"},
239+
{
240+
"name": "rad-repo",
241+
"identifier": "Example/rad-repo",
242+
"default_branch": "main",
243+
"external_id": "rad-repo",
244+
},
197245
]
198246
response = self.client.get(
199247
self.path, format="json", data={"search": "rad", "accessibleOnly": "true"}
@@ -208,6 +256,7 @@ def test_accessible_only_passes_param(self, get_repositories: MagicMock) -> None
208256
"identifier": "Example/rad-repo",
209257
"defaultBranch": "main",
210258
"isInstalled": False,
259+
"externalId": "rad-repo",
211260
},
212261
],
213262
"searchable": True,
@@ -219,7 +268,12 @@ def test_accessible_only_passes_param(self, get_repositories: MagicMock) -> None
219268
def test_accessible_only_without_search(self, get_repositories: MagicMock) -> None:
220269
"""When accessibleOnly=true but no search, passes both params through."""
221270
get_repositories.return_value = [
222-
{"name": "rad-repo", "identifier": "Example/rad-repo", "default_branch": "main"},
271+
{
272+
"name": "rad-repo",
273+
"identifier": "Example/rad-repo",
274+
"default_branch": "main",
275+
"external_id": "rad-repo",
276+
},
223277
]
224278
response = self.client.get(self.path, format="json", data={"accessibleOnly": "true"})
225279

@@ -232,8 +286,18 @@ def test_accessible_only_without_search(self, get_repositories: MagicMock) -> No
232286
def test_accessible_only_with_installable_only(self, get_repositories: MagicMock) -> None:
233287
"""Both filters compose: accessible scopes the fetch, installable excludes installed repos."""
234288
get_repositories.return_value = [
235-
{"name": "rad-repo", "identifier": "Example/rad-repo", "default_branch": "main"},
236-
{"name": "cool-repo", "identifier": "Example/cool-repo", "default_branch": "dev"},
289+
{
290+
"name": "rad-repo",
291+
"identifier": "Example/rad-repo",
292+
"default_branch": "main",
293+
"external_id": "rad-repo",
294+
},
295+
{
296+
"name": "cool-repo",
297+
"identifier": "Example/cool-repo",
298+
"default_branch": "dev",
299+
"external_id": "cool-repo",
300+
},
237301
]
238302

239303
self.create_repo(
@@ -257,6 +321,7 @@ def test_accessible_only_with_installable_only(self, get_repositories: MagicMock
257321
"identifier": "Example/cool-repo",
258322
"defaultBranch": "dev",
259323
"isInstalled": False,
324+
"externalId": "cool-repo",
260325
},
261326
],
262327
"searchable": True,

0 commit comments

Comments
 (0)