Skip to content

Commit 78599db

Browse files
authored
feat(api): Support Project ID and Slugs in OrganizationArtifactBundleAssembleEndpoint (#74232)
I am working on getting all the commands in our Sentry CLI to support Ids as well as Slugs. [rough spec](https://www.notion.so/sentry/Supporting-IDs-and-Slugs-in-Sentry-CLI-35b805ea169e47348805672cdee41297?pm=c) We have already gone through the effort of supporting ids and slugs in path parameters of all the APIs in our codebase. There are some endpoints that the CLI uses that pass in project slugs as _body parameters_, so we need to support Ids for those as well. Here, I use the fact that ids are numeric and slugs aren't to process both type of identifiers.
1 parent 620181b commit 78599db

File tree

2 files changed

+100
-9
lines changed

2 files changed

+100
-9
lines changed

src/sentry/api/endpoints/organization_artifactbundle_assemble.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import jsonschema
22
import orjson
3+
from django.db.models import Q
34
from rest_framework.request import Request
45
from rest_framework.response import Response
56

@@ -56,16 +57,26 @@ def post(self, request: Request, organization) -> Response:
5657
except Exception:
5758
return Response({"error": "Invalid json body"}, status=400)
5859

59-
projects = set(data.get("projects", []))
60-
if len(projects) == 0:
60+
input_projects = data.get("projects", [])
61+
if len(input_projects) == 0:
6162
return Response({"error": "You need to specify at least one project"}, status=400)
6263

63-
project_ids = list(
64-
Project.objects.filter(
65-
organization=organization, status=ObjectStatus.ACTIVE, slug__in=projects
66-
).values_list("id", flat=True)
67-
)
68-
if len(project_ids) != len(projects):
64+
input_project_slug = set()
65+
input_project_id = set()
66+
for project in input_projects:
67+
# IDs are always numeric, slugs cannot be numeric
68+
if str(project).isdecimal():
69+
input_project_id.add(project)
70+
else:
71+
input_project_slug.add(project)
72+
73+
project_ids = Project.objects.filter(
74+
(Q(id__in=input_project_id) | Q(slug__in=input_project_slug)),
75+
organization=organization,
76+
status=ObjectStatus.ACTIVE,
77+
).values_list("id", flat=True)
78+
79+
if len(project_ids) != len(input_projects):
6980
return Response({"error": "One or more projects are invalid"}, status=400)
7081

7182
if not self.has_release_permission(request, organization, project_ids=set(project_ids)):
@@ -131,6 +142,6 @@ def post(self, request: Request, organization) -> Response:
131142
)
132143

133144
if is_org_auth_token_auth(request.auth):
134-
update_org_auth_token_last_used(request.auth, project_ids)
145+
update_org_auth_token_last_used(request.auth, list(project_ids))
135146

136147
return Response({"state": ChunkFileState.CREATED, "missingChunks": []}, status=200)

tests/sentry/api/endpoints/test_organization_artifactbundle_assemble.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,86 @@ def test_assemble_with_invalid_projects(self):
141141
assert response.status_code == 400, response.content
142142
assert response.data["error"] == "One or more projects are invalid"
143143

144+
def test_assemble_with_valid_project_slugs(self):
145+
# Test with all valid project slugs
146+
valid_project = self.create_project()
147+
another_valid_project = self.create_project()
148+
149+
bundle_file = self.create_artifact_bundle_zip(
150+
org=self.organization.slug, release=self.release.version
151+
)
152+
total_checksum = sha1(bundle_file).hexdigest()
153+
154+
blob = FileBlob.from_file(ContentFile(bundle_file))
155+
FileBlobOwner.objects.get_or_create(organization_id=self.organization.id, blob=blob)
156+
157+
response = self.client.post(
158+
self.url,
159+
data={
160+
"checksum": total_checksum,
161+
"chunks": [blob.checksum],
162+
"projects": [valid_project.slug, another_valid_project.slug],
163+
},
164+
HTTP_AUTHORIZATION=f"Bearer {self.token.token}",
165+
)
166+
167+
self.assertEqual(response.status_code, 200)
168+
169+
def test_assemble_with_valid_project_ids(self):
170+
# Test with all valid project IDs
171+
valid_project = self.create_project()
172+
another_valid_project = self.create_project()
173+
174+
bundle_file = self.create_artifact_bundle_zip(
175+
org=self.organization.slug, release=self.release.version
176+
)
177+
total_checksum = sha1(bundle_file).hexdigest()
178+
179+
blob = FileBlob.from_file(ContentFile(bundle_file))
180+
FileBlobOwner.objects.get_or_create(organization_id=self.organization.id, blob=blob)
181+
182+
response = self.client.post(
183+
self.url,
184+
data={
185+
"checksum": total_checksum,
186+
"chunks": [blob.checksum],
187+
"projects": [str(valid_project.id), str(another_valid_project.id)],
188+
},
189+
HTTP_AUTHORIZATION=f"Bearer {self.token.token}",
190+
)
191+
192+
self.assertEqual(response.status_code, 200)
193+
194+
def test_assemble_with_mix_of_slugs_and_ids(self):
195+
# Test with a mix of valid project slugs and IDs
196+
valid_project = self.create_project()
197+
another_valid_project = self.create_project()
198+
third_valid_project = self.create_project()
199+
200+
bundle_file = self.create_artifact_bundle_zip(
201+
org=self.organization.slug, release=self.release.version
202+
)
203+
total_checksum = sha1(bundle_file).hexdigest()
204+
205+
blob = FileBlob.from_file(ContentFile(bundle_file))
206+
FileBlobOwner.objects.get_or_create(organization_id=self.organization.id, blob=blob)
207+
208+
response = self.client.post(
209+
self.url,
210+
data={
211+
"checksum": total_checksum,
212+
"chunks": [blob.checksum],
213+
"projects": [
214+
valid_project.slug,
215+
str(another_valid_project.id),
216+
str(third_valid_project.id),
217+
],
218+
},
219+
HTTP_AUTHORIZATION=f"Bearer {self.token.token}",
220+
)
221+
222+
self.assertEqual(response.status_code, 200)
223+
144224
@patch("sentry.tasks.assemble.assemble_artifacts")
145225
def test_assemble_without_version_and_dist(self, mock_assemble_artifacts):
146226
bundle_file = self.create_artifact_bundle_zip(

0 commit comments

Comments
 (0)