Skip to content

Commit 25afb9c

Browse files
authored
feat(preprod): add basic base build info to build details api (EME-679) (#105145)
Adds base_build_info `build_details/` api response. This is needed to support upcoming frontend changes to the size analysis build details page build metadata table.
1 parent 197fd04 commit 25afb9c

File tree

2 files changed

+104
-47
lines changed

2 files changed

+104
-47
lines changed

src/sentry/preprod/api/models/project_preprod_build_details_models.py

Lines changed: 52 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ class BuildDetailsApiResponse(BaseModel):
132132
size_info: SizeInfo | None = None
133133
posted_status_checks: PostedStatusChecks | None = None
134134
base_artifact_id: str | None = None
135+
base_build_info: BuildDetailsAppInfo | None = None
135136

136137

137138
def platform_from_artifact_type(artifact_type: PreprodArtifact.ArtifactType) -> Platform:
@@ -146,6 +147,53 @@ def platform_from_artifact_type(artifact_type: PreprodArtifact.ArtifactType) ->
146147
raise ValueError(f"Unknown artifact type: {artifact_type}")
147148

148149

150+
def create_build_details_app_info(artifact: PreprodArtifact) -> BuildDetailsAppInfo:
151+
"""Factory function to create BuildDetailsAppInfo from a PreprodArtifact."""
152+
platform = None
153+
# artifact_type can be null before preprocessing has completed
154+
if artifact.artifact_type is not None:
155+
platform = platform_from_artifact_type(artifact.artifact_type)
156+
157+
apple_app_info = None
158+
if platform == Platform.IOS or platform == Platform.MACOS:
159+
legacy_missing_dsym_binaries = (
160+
artifact.extras.get("missing_dsym_binaries", []) if artifact.extras else []
161+
)
162+
has_missing_dsym_binaries = (
163+
artifact.extras.get("has_missing_dsym_binaries", False)
164+
or len(legacy_missing_dsym_binaries) > 0
165+
if artifact.extras
166+
else False
167+
)
168+
apple_app_info = AppleAppInfo(has_missing_dsym_binaries=has_missing_dsym_binaries)
169+
170+
android_app_info = None
171+
if platform == Platform.ANDROID:
172+
android_app_info = AndroidAppInfo(
173+
has_proguard_mapping=(
174+
artifact.extras.get("has_proguard_mapping", True) if artifact.extras else True
175+
)
176+
)
177+
178+
return BuildDetailsAppInfo(
179+
app_id=artifact.app_id,
180+
name=artifact.app_name,
181+
version=artifact.build_version,
182+
build_number=artifact.build_number,
183+
date_added=(artifact.date_added.isoformat() if artifact.date_added else None),
184+
date_built=(artifact.date_built.isoformat() if artifact.date_built else None),
185+
artifact_type=artifact.artifact_type,
186+
platform=platform,
187+
is_installable=is_installable_artifact(artifact),
188+
build_configuration=(
189+
artifact.build_configuration.name if artifact.build_configuration else None
190+
),
191+
app_icon_id=artifact.app_icon_id,
192+
apple_app_info=apple_app_info,
193+
android_app_info=android_app_info,
194+
)
195+
196+
149197
def to_size_info(
150198
size_metrics: list[PreprodArtifactSizeMetrics],
151199
base_size_metrics: list[PreprodArtifactSizeMetrics] | None = None,
@@ -219,62 +267,18 @@ def transform_preprod_artifact_to_build_details(
219267

220268
base_size_metrics_list: list[PreprodArtifactSizeMetrics] = []
221269
base_artifact = artifact.get_base_artifact_for_commit().first()
270+
base_build_info = None
222271
if base_artifact:
223272
base_size_metrics_qs = PreprodArtifactSizeMetrics.objects.filter(
224273
preprod_artifact=base_artifact,
225274
state=PreprodArtifactSizeMetrics.SizeAnalysisState.COMPLETED,
226275
)
227276
base_size_metrics_list = list(base_size_metrics_qs)
277+
base_build_info = create_build_details_app_info(base_artifact)
228278

229279
size_info = to_size_info(size_metrics_list, base_size_metrics_list)
230280

231-
platform = None
232-
# artifact_type can be null before preprocessing has completed
233-
if artifact.artifact_type is not None:
234-
platform = platform_from_artifact_type(artifact.artifact_type)
235-
236-
apple_app_info = None
237-
if platform == Platform.IOS or platform == Platform.MACOS:
238-
legacy_missing_dsym_binaries = (
239-
artifact.extras.get("missing_dsym_binaries", []) if artifact.extras else []
240-
)
241-
has_missing_dsym_binaries = (
242-
artifact.extras.get("has_missing_dsym_binaries", False)
243-
or len(legacy_missing_dsym_binaries) > 0
244-
if artifact.extras
245-
else False
246-
)
247-
apple_app_info = AppleAppInfo(has_missing_dsym_binaries=has_missing_dsym_binaries)
248-
249-
android_app_info = None
250-
if platform == Platform.ANDROID:
251-
android_app_info = AndroidAppInfo(
252-
has_proguard_mapping=(
253-
artifact.extras.get("has_proguard_mapping", True) if artifact.extras else True
254-
)
255-
)
256-
257-
app_info = BuildDetailsAppInfo(
258-
app_id=artifact.app_id,
259-
name=artifact.app_name,
260-
version=artifact.build_version,
261-
build_number=artifact.build_number,
262-
date_added=(artifact.date_added.isoformat() if artifact.date_added else None),
263-
date_built=(artifact.date_built.isoformat() if artifact.date_built else None),
264-
artifact_type=artifact.artifact_type,
265-
platform=(
266-
platform_from_artifact_type(artifact.artifact_type)
267-
if artifact.artifact_type is not None
268-
else None
269-
),
270-
is_installable=is_installable_artifact(artifact),
271-
build_configuration=(
272-
artifact.build_configuration.name if artifact.build_configuration else None
273-
),
274-
app_icon_id=artifact.app_icon_id,
275-
apple_app_info=apple_app_info,
276-
android_app_info=android_app_info,
277-
)
281+
app_info = create_build_details_app_info(artifact)
278282

279283
vcs_info = BuildDetailsVcsInfo(
280284
head_sha=(artifact.commit_comparison.head_sha if artifact.commit_comparison else None),
@@ -301,6 +305,7 @@ def transform_preprod_artifact_to_build_details(
301305
size_info=size_info,
302306
posted_status_checks=posted_status_checks,
303307
base_artifact_id=base_artifact.id if base_artifact else None,
308+
base_build_info=base_build_info,
304309
)
305310

306311

tests/sentry/preprod/api/endpoints/test_project_preprod_build_details.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,3 +452,55 @@ def test_posted_status_checks_failure_with_invalid_error_type(self) -> None:
452452
assert resp_data["posted_status_checks"] is not None
453453
assert resp_data["posted_status_checks"]["size"]["success"] is False
454454
assert resp_data["posted_status_checks"]["size"]["error_type"] is None
455+
456+
def test_base_build_info_when_base_artifact_exists(self) -> None:
457+
"""Test that base_build_info is included when a base artifact exists."""
458+
assert self.preprod_artifact.commit_comparison is not None
459+
base_commit_comparison = self.create_commit_comparison(
460+
organization=self.org,
461+
head_sha=self.preprod_artifact.commit_comparison.base_sha,
462+
base_sha="0000000000000000000000000000000000000000",
463+
)
464+
base_file = self.create_file(name="base_artifact.apk", type="application/octet-stream")
465+
base_artifact = self.create_preprod_artifact(
466+
project=self.project,
467+
file_id=base_file.id,
468+
artifact_type=self.preprod_artifact.artifact_type,
469+
app_id=self.preprod_artifact.app_id,
470+
app_name=self.preprod_artifact.app_name,
471+
build_version="0.9.0",
472+
build_number=41,
473+
commit_comparison=base_commit_comparison,
474+
)
475+
476+
url = self._get_url()
477+
response = self.client.get(
478+
url, format="json", HTTP_AUTHORIZATION=f"Bearer {self.api_token.token}"
479+
)
480+
481+
assert response.status_code == 200
482+
resp_data = response.json()
483+
assert resp_data["base_artifact_id"] == str(base_artifact.id)
484+
assert resp_data["base_build_info"] is not None
485+
# base_build_info now returns full BuildDetailsAppInfo
486+
assert resp_data["base_build_info"]["version"] == "0.9.0"
487+
assert resp_data["base_build_info"]["build_number"] == 41
488+
assert resp_data["base_build_info"]["app_id"] == base_artifact.app_id
489+
assert resp_data["base_build_info"]["name"] == base_artifact.app_name
490+
assert resp_data["base_build_info"]["artifact_type"] == base_artifact.artifact_type
491+
assert "date_added" in resp_data["base_build_info"]
492+
assert "date_built" in resp_data["base_build_info"]
493+
assert "is_installable" in resp_data["base_build_info"]
494+
assert "platform" in resp_data["base_build_info"]
495+
496+
def test_base_build_info_none_when_no_base_artifact(self) -> None:
497+
"""Test that base_build_info is None when no base artifact exists."""
498+
url = self._get_url()
499+
response = self.client.get(
500+
url, format="json", HTTP_AUTHORIZATION=f"Bearer {self.api_token.token}"
501+
)
502+
503+
assert response.status_code == 200
504+
resp_data = response.json()
505+
assert resp_data["base_artifact_id"] is None
506+
assert resp_data["base_build_info"] is None

0 commit comments

Comments
 (0)