-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
feat(preprod): Add preprod images download endpoint #102117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
src/sentry/preprod/api/endpoints/project_preprod_artifact_icon.py
Outdated
Show resolved
Hide resolved
src/sentry/preprod/api/endpoints/project_preprod_artifact_image.py
Outdated
Show resolved
Hide resolved
c5561d3 to
6835a33
Compare
| @analytics.eventclass("preprod_artifact.api.size_analysis_download") | ||
| class PreprodArtifactApiSizeAnalysisDownloadEvent(analytics.Event): | ||
| @analytics.eventclass("preprod_artifact.api.get_build_details") | ||
| class PreprodArtifactApiGetBuildDetailsEvent(analytics.Event): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't change these, I just reordered a few of these to match the .register order below
src/sentry/preprod/api/endpoints/project_preprod_artifact_image.py
Outdated
Show resolved
Hide resolved
src/sentry/preprod/api/endpoints/project_preprod_artifact_image.py
Outdated
Show resolved
Hide resolved
src/sentry/preprod/api/endpoints/project_preprod_artifact_image.py
Outdated
Show resolved
Hide resolved
❌ 2 Tests Failed:
View the top 2 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def detect_image_content_type(image_data: bytes) -> str: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From Jan's comment below it sounds like eventually this can be set on write and we wouldn't have to do it here on read, but this seems ok for now to unblock testing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, before merge I'll leverage a custom metadata field and we can use the first class one as soon as Jan & team land that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be helpers for this in the objectstore client now: https://sentry.slack.com/archives/C08S3SK6JEN/p1763139914180699?thread_ts=1762422386.367739&cid=C08S3SK6JEN
35dbef3 to
36eeea1
Compare
| platform: Platform | None = None | ||
| is_installable: bool | ||
| build_configuration: str | None = None | ||
| app_icon_id: str | None = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is related to app icon, not the generic download endpoint, but should be fine to merge as the frontend does not yet use this until #102118 lands
36eeea1 to
4f0e1e9
Compare
| return _ATTACHMENTS_CLIENT.session(_ATTACHMENTS_USECASE, org=org, project=project) | ||
|
|
||
|
|
||
| def get_preprod_session(org: int, project: int) -> Session: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jan-auer saw this practice already used for attachments, is this how you all would recommend using a global session?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. Although you might as well create a dict mapping Usecase -> Client | None to store the usecase and the client, as I can see us adding more of these over time.
4f0e1e9 to
24d9e1d
Compare
| "image_id": image_id, | ||
| }, | ||
| ) | ||
| return HttpResponse({"error": "Internal server error"}, status=500) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Invalid dictionary passed to HttpResponse without JSON serialization
The error response passes a dictionary directly to HttpResponse instead of serializing it to JSON. HttpResponse expects string or bytes content, and will convert the dict to its Python string representation (e.g., "{'error': '...'}") instead of valid JSON. Use json.dumps() to serialize the dictionary first.
| "image_id": image_id, | ||
| }, | ||
| ) | ||
| return HttpResponse({"error": "Internal server error"}, status=500) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Invalid dictionary passed to HttpResponse without JSON serialization
The error response passes a dictionary directly to HttpResponse instead of serializing it to JSON. HttpResponse expects string or bytes content, and will convert the dict to its Python string representation (e.g., "{'error': '...'}") instead of valid JSON. Use json.dumps() to serialize the dictionary first, or use Response from rest_framework.
|
🚨 Warning: This pull request contains Frontend and Backend changes! It's discouraged to make changes to Sentry's Frontend and Backend in a single pull request. The Frontend and Backend are not atomically deployed. If the changes are interdependent of each other, they must be separated into two pull requests and be made forward or backwards compatible, such that the Backend or Frontend can be safely deployed independently. Have questions? Please ask in the |
7d51dc7 to
370b825
Compare
7aedd97 to
25db450
Compare
5d540ea to
f7a9713
Compare
| "image_id": image_id, | ||
| }, | ||
| ) | ||
| return HttpResponse({"error": "Internal server error"}, status=500) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: The HttpResponse constructor at line 55 receives a dictionary, which it does not handle correctly, leading to a TypeError or malformed response.
Severity: HIGH | Confidence: High
🔍 Detailed Analysis
The HttpResponse constructor at line 55 is called with a Python dictionary {'error': 'Internal server error'} as its first argument. Django's HttpResponse expects content to be bytes or a string, not a dictionary. This will cause a TypeError when an exception occurs in the try block (line 39), preventing a graceful 500 error response and instead crashing the server.
💡 Suggested Fix
Serialize the dictionary to JSON: HttpResponse(json.dumps({'error': 'Internal server error'}), content_type='application/json', status=500) or return plain text: HttpResponse('Internal server error', status=500).
🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: src/sentry/preprod/api/endpoints/project_preprod_artifact_image.py#L55
Potential issue: The `HttpResponse` constructor at line 55 is called with a Python
dictionary `{'error': 'Internal server error'}` as its first argument. Django's
`HttpResponse` expects content to be bytes or a string, not a dictionary. This will
cause a `TypeError` when an exception occurs in the `try` block (line 39), preventing a
graceful 500 error response and instead crashing the server.
Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 3502411

Adds a new preprod
/files/images/<image_id>endpoint which allows our frontend to download images from an ID with objectstore. First to be used with app icons coming soon.Tested fully E2E locally and all works well. To work, this is reliant on landing getsentry/launchpad#430 once we get a published version of the objectstore client, but since nobody will be consuming this endpoint until we land the stacked PR (#102118) this is safe to go ahead and merge.