diff --git a/src/sentry/preprod/api/endpoints/organization_preprod_artifact_assemble.py b/src/sentry/preprod/api/endpoints/organization_preprod_artifact_assemble.py index e91b61c0a0358c..1d692b6627430f 100644 --- a/src/sentry/preprod/api/endpoints/organization_preprod_artifact_assemble.py +++ b/src/sentry/preprod/api/endpoints/organization_preprod_artifact_assemble.py @@ -14,6 +14,7 @@ from sentry.models.orgauthtoken import is_org_auth_token_auth, update_org_auth_token_last_used from sentry.preprod.analytics import PreprodArtifactApiAssembleEvent from sentry.preprod.tasks import assemble_preprod_artifact, create_preprod_artifact +from sentry.preprod.url_utils import get_preprod_artifact_url from sentry.tasks.assemble import ChunkFileState from sentry.types.ratelimit import RateLimit, RateLimitCategory @@ -183,6 +184,12 @@ def post(self, request: Request, project) -> Response: if is_org_auth_token_auth(request.auth): update_org_auth_token_last_used(request.auth, [project.id]) + artifact_url = get_preprod_artifact_url(project.organization_id, artifact_id) + return Response( - {"state": ChunkFileState.OK, "missingChunks": [], "artifactId": artifact_id} + { + "state": ChunkFileState.CREATED, + "missingChunks": [], + "artifactUrl": artifact_url, + } ) diff --git a/src/sentry/preprod/url_utils.py b/src/sentry/preprod/url_utils.py new file mode 100644 index 00000000000000..28337dc0b31211 --- /dev/null +++ b/src/sentry/preprod/url_utils.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from sentry.models.organization import Organization + + +def get_preprod_artifact_url(organization_id: int, artifact_id: str) -> str: + """ + Build a region/customer-domain aware absolute URL for the preprod artifact UI. + """ + organization: Organization = Organization.objects.get_from_cache(id=organization_id) + + path = f"/organizations/{organization.slug}/preprod/internal/{artifact_id}" + return organization.absolute_url(path) diff --git a/tests/sentry/preprod/api/endpoints/test_organization_preprod_artifact_assemble.py b/tests/sentry/preprod/api/endpoints/test_organization_preprod_artifact_assemble.py index 1e2720b843dae4..d1f2f39fc08aa4 100644 --- a/tests/sentry/preprod/api/endpoints/test_organization_preprod_artifact_assemble.py +++ b/tests/sentry/preprod/api/endpoints/test_organization_preprod_artifact_assemble.py @@ -374,9 +374,10 @@ def test_assemble_basic( HTTP_AUTHORIZATION=f"Bearer {self.token.token}", ) assert response.status_code == 200, response.content - assert response.data["state"] == ChunkFileState.OK + assert response.data["state"] == ChunkFileState.CREATED assert set(response.data["missingChunks"]) == set() - assert response.data["artifactId"] == artifact_id + expected_url = f"/organizations/{self.organization.slug}/preprod/internal/{artifact_id}" + assert expected_url in response.data["artifactUrl"] mock_create_preprod_artifact.assert_called_once_with( org_id=self.organization.id, @@ -440,9 +441,10 @@ def test_assemble_with_metadata( HTTP_AUTHORIZATION=f"Bearer {self.token.token}", ) assert response.status_code == 200, response.content - assert response.data["state"] == ChunkFileState.OK + assert response.data["state"] == ChunkFileState.CREATED assert set(response.data["missingChunks"]) == set() - assert response.data["artifactId"] == artifact_id + expected_url = f"/organizations/{self.organization.slug}/preprod/internal/{artifact_id}" + assert expected_url in response.data["artifactUrl"] mock_create_preprod_artifact.assert_called_once_with( org_id=self.organization.id, @@ -500,7 +502,7 @@ def test_assemble_with_missing_chunks(self) -> None: ) assert response.status_code == 200, response.content - assert response.data["state"] == ChunkFileState.OK + assert response.data["state"] == ChunkFileState.CREATED def test_assemble_response(self) -> None: content = b"test response content" @@ -518,7 +520,7 @@ def test_assemble_response(self) -> None: ) assert response.status_code == 200, response.content - assert response.data["state"] == ChunkFileState.OK + assert response.data["state"] == ChunkFileState.CREATED def test_assemble_with_pending_deletion_project(self) -> None: self.project.status = ObjectStatus.PENDING_DELETION @@ -637,7 +639,7 @@ def test_check_existing_assembly_status(self) -> None: # Even if assembly status exists, endpoint doesn't check it set_assemble_status( - AssembleTask.PREPROD_ARTIFACT, self.project.id, checksum, ChunkFileState.OK + AssembleTask.PREPROD_ARTIFACT, self.project.id, checksum, ChunkFileState.CREATED ) response = self.client.post( @@ -671,7 +673,7 @@ def test_integration_task_sets_status_api_can_read_it(self) -> None: # Even if task sets status, this endpoint doesn't read it set_assemble_status( - AssembleTask.PREPROD_ARTIFACT, self.project.id, total_checksum, ChunkFileState.OK + AssembleTask.PREPROD_ARTIFACT, self.project.id, total_checksum, ChunkFileState.CREATED ) response = self.client.post(