feat(catalog): enable logo file upload in theme editor#4781
Conversation
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review.
Tip: disable this comment in your organization's Code Review settings.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #4781 +/- ##
==========================================
+ Coverage 45.69% 45.83% +0.13%
==========================================
Files 829 829
Lines 33532 33551 +19
Branches 5698 5702 +4
==========================================
+ Hits 15323 15377 +54
+ Misses 16212 16178 -34
+ Partials 1997 1996 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Test results against quilt-staging (us-east-1)All 9/9 checks passed after running the E2E test suite with a headed Playwright browser against the live nightly stack. Results
Infrastructure change requiredThe {
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::${ServiceBucket}/catalog/logo.*",
"Effect": "Allow"
}Applied to staging manually for testing; needs to be added to the stack CDK/CFN definition before deploying to other stacks. |
9246a6e to
d7f67f3
Compare
Implement useUploadFile to upload the logo image to the service bucket at catalog/logo.<ext> and return an s3:// URL, then wire it up by removing the useThirdPartyDomainForLogo flag so the drag-and-drop InputFile component is shown instead of the URL text field. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pre-existing formatting issues caught by CI lint check after lock file regeneration pulled updated prettier rules. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-existing formatting issues detected by CI's prettier 3.4.2. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The flag was a temporary guard for the URL-only logo input. Now that file upload is implemented, remove the flag, the unused URL-input branch, and the unused Form import. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…itor Allow admins to paste a logo URL in addition to drag-and-drop file upload, improving flexibility for theme customization. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2d3f855 to
a4f4cd8
Compare
There was a problem hiding this comment.
I don't understand, how does it work, if it works.
I see that ThemeEditor uploads the file, and it returns s3:// URL and writes it to CatalogSettings. But we use that URL in app/containers/NavBar/NavBar.tsx to render the image. Shouldn't that URL be https://bucket/proxy/something instead of s3://?
Ah, I see
Covers extension-based S3 key computation, no-extension fallback, and multi-dot filename handling to satisfy codecov patch coverage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ThemeEditor: revoke object URLs in InputFile preview to fix memory leak - ThemeEditor: restrict logo dropzone to image/* files - CatalogSettings.spec: type putObject mock so call args are typed Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers placeholder, URL string, File preview with createObjectURL/ revokeObjectURL lifecycle, and URL text-field onChange. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
a4f4cd8 to
638e6ef
Compare
…ploadFile
Per fiskus review (PR 4781):
- Use `extname` from `path` instead of hand-rolled split logic.
- Capture `VersionId` from `putObject` and return `S3ObjectLocation`
({bucket, key, version}) instead of a stringified s3:// URL; the
caller stringifies via `s3paths.handleToS3Url`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Maksim Chervonnyi <mail@redmax.dev>
- Use root style for grid layout instead of empty object - Add error handling and meta props to InputFile component - Use renderHook from @testing-library/react-hooks in tests - Remove unused Model import
Per fiskus review (PR #4781): switch to `import type * as Model from 'model'` so the upload return type and spec variable both reference `Model.S3.S3ObjectLocation`. Replaces the broken `AWS.S3ObjectLocation` introduced in bb72444, which doesn't exist on the AWS namespace and broke the test-catalog build. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Grid gap already provides spacing; the margin doubled it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Decision to merge is up to you, but consider that after merging this PR, frontend will be in a slightly broken state: logo change would not work. I disabled auto-merge for now. |
|
Happy to wait on this merge until deployment is approved. I agree that technically it is inconsistent, but no deployment will actually be "broken" until some stack actually pins the new image, right? Which is precisely what the deployment PR does. |
Derive the upload key from file.type (MIME) rather than extname(file.name), restrict the dropzone to the same allowlist, and reject anything else. Drops SVG (inline <script> in SVG executes on direct navigation, which is exactly the public-bucket scenario the IAM grant covers). Also fixes an empty-extension AccessDenied when files arrived without a filename extension (clipboard pastes, stripped names) — the pre-MIME logic produced key='catalog/logo' which doesn't match the IAM allowlist. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
catalog/app/containers/Admin/Settings/ThemeEditor.tsx:136
onDropunconditionally callsonChange(files[0]). When a user drops a rejected file type (or no accepted files),filescan be empty and this will set the form value toundefined(clearing a previously set URL/file). Guard onfiles.length(and optionally surfacefileRejectionsfor feedback) before callingonChange.
const onDrop = React.useCallback(
(files: FileWithPath[]) => {
onChange(files[0])
},
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address Copilot review on PR #4781: - onSubmit values type now allows FileWithPath alongside string, so the upload branch type-checks accurately rather than narrowing to never. - onDrop guards files.length so a rejected/empty drop no longer clears a previously set logo value. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@greptileai please re-review |
sir-sigurd
left a comment
There was a problem hiding this comment.
This review was generated with Claude. I don't have frontend expertise — take at your own risk.
LGTM on the narrow-scope delta: the diff between 92fa91e6 (fiskus's last review touchpoint) and 915893b5 (current head) — two commits, 68246c09 (pin the MIME allowlist) and 915893b5 (widen logoUrl type + guard empty drops).
What I checked:
LOGO_MIME_TO_EXTkeys +UnsupportedLogoTypeErroralign with the deployment side'sCATALOG_LOGO_EXTENSIONS/CATALOG_LOGO_CONTENT_TYPES/s3:content-typeIAM Condition.ContentType: file.typealways set.- Dropzone
acceptderived fromACCEPTED_LOGO_MIME_TYPES— single source of truth across upload, dropzone, and the IAM allowlist on the deployment side. - onSubmit type-narrowing reads cleanly through
string,'',FileWithPath,undefined. - onDrop empty-guard prevents clearing a previously-set logo on rejected/empty drops.
- Tests cover MIME→key for the four allowed types, SVG rejection, empty-MIME rejection, filename-ignored.
Minor / take-or-leave:
UnsupportedLogoTypeErrorthrown fromuseUploadFilefalls through onSubmit's generic catch as "Couldn't save settings, see console for details." The dropzone'sacceptfilter is keyed on the same MIME allowlist, so this exception shouldn't fire from the UI in practice — but if it ever does (test path, future caller, browser MIME-sniffing oddity), the user gets a misleading error. Could surface a specific message viainstanceof UnsupportedLogoTypeErrorin the catch.
Not in scope: the broader PR diff and Greptile's P2 polish items.
|
@drernie no changelog entry -- intentional? |
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
What is changing
useUploadFileinCatalogSettings.tsx— writes the dropped file tocatalog/logo.<ext>in the service bucket and returns itss3://URL.ThemeEditor.tsx— drops theuseThirdPartyDomainForLogogate so the drag-and-dropInputFilerenders alongside the URL field.Logorender component is unchanged; it already signss3://URLs.Why
The theme editor previously accepted only an externally hosted logo URL — every operator had to host their logo image themselves, adding onboarding friction. The display path already supported
s3://URLs; the missing piece was a write path from the browser into the service bucket.Verification
tf-dev-unstable: drag-and-drop and pasted-URL flows both succeed end-to-end.Notes
logo.pngafter upload oflogo.jpg) are not cleaned up — orphaned files are inert oncesettings.jsonupdates the URL.Related PRs
stack/unstabletracking PR carrying the smoke deploy.Greptile Summary
This PR activates the logo file-upload path in the theme editor:
useUploadFilenow writes tocatalog/logo.<ext>in the service bucket (keyed by MIME type, SVG intentionally excluded for XSS reasons), andInputFilegains a URL text field alongside the dropzone so operators can use either input method. Both the upload hook and theInputFilecomponent are covered by new unit tests.Confidence Score: 5/5
Safe to merge; no logic-breaking defects found, only minor P2 polish items.
All findings are P2 (missing alt attribute and unforwarded disabled prop). The core upload and URL-conversion logic is correct, MIME allowlist aligns with the IAM policy, and SVG is excluded with a clear security rationale. New tests cover the critical paths.
ThemeEditor.tsx — InputFile should accept and forward a disabled prop
Important Files Changed
useUploadFile: derivescatalog/logo.<ext>key from MIME type, uploads via S3putObject, returnsS3ObjectLocation. SVG is intentionally excluded with a clear security comment. Allowlist is pinned to IAM constraints.useThirdPartyDomainForLogoflag and dead code; rendersInputFileunconditionally. RefactorsInputFileto show both a dropzone and a URL text field.disabledprop fromRF.Fieldis silently ignored, leaving fields interactive during submission.useUploadFile: covers MIME-to-key mapping for all four accepted types, rejects SVG and empty MIME, and verifies filename is ignored in favour of MIME type.InputFileunit tests: verifies empty state, URL string rendering, object-URL lifecycle (create + revoke on unmount), and text-field onChange wiring.Sequence Diagram
sequenceDiagram actor Operator participant InputFile participant RF.Field participant onSubmit participant useUploadFile participant S3 participant useWriteSettings Operator->>InputFile: Drop image file InputFile->>RF.Field: onChange(File) Operator->>InputFile: (or) type URL InputFile->>RF.Field: onChange(string) Operator->>onSubmit: click Save alt value is File onSubmit->>useUploadFile: uploadFile(File) useUploadFile->>S3: putObject(catalog/logo.<ext>) S3-->>useUploadFile: VersionId useUploadFile-->>onSubmit: S3ObjectLocation onSubmit->>onSubmit: handleToS3Url else value is string URL onSubmit->>onSubmit: use string as-is end onSubmit->>useWriteSettings: writeSettings useWriteSettings->>S3: putObject(catalog/settings.json) S3-->>useWriteSettings: ok onSubmit-->>Operator: dialog closesReviews (2): Last reviewed commit: "fix(catalog): widen logoUrl type and gua..." | Re-trigger Greptile