Skip to content

fix(catalog): resilient Logo render for malformed S3 URLs#4881

Merged
fiskus merged 17 commits into
masterfrom
fix-catalog-logo-malformed-s3-url
May 8, 2026
Merged

fix(catalog): resilient Logo render for malformed S3 URLs#4881
fiskus merged 17 commits into
masterfrom
fix-catalog-logo-malformed-s3-url

Conversation

@fiskus
Copy link
Copy Markdown
Member

@fiskus fiskus commented May 7, 2026

Summary

  • <Logo> falls back to the default Quilt logo (and reports to Sentry) when the configured src is a malformed s3:// URL, instead of crashing the render tree via parseS3Url throwing.
  • New s3paths.tryParseS3Url — a null-returning, render-safe counterpart to parseS3Url.
  • New validators.urlWithHost — stricter sibling of validators.url that rejects URLs with no hostname (e.g. s3://, which new URL() accepts because non-special schemes are allowed empty hosts). Used in the logo URL field so partial inputs like s3:// are caught at validation time, not silently masked by <Logo>'s runtime fallback.
  • Theme editor:
    • Adds validators.urlWithHost and validators.s3Url to the logo URL field's compose chain.
    • Surfaces validation errors as the user types (meta.modified || meta.submitFailed) instead of only after submit.
    • Extracts a small Preview sub-component; renders a broken_image placeholder for invalid URLs and a hide_image placeholder for the empty state.
    • Keeps the broken-image icon in sync with the helperText (both gated through the same error flag).

Test plan

  • npm run test:only -- validators Logo Admin/Settings/ThemeEditor passes (14 + 5 + 4 tests).
  • Manual: configure a custom logo with s3:// (no bucket) — broken-image preview + "Enter a valid URL (https:// or s3://)" helper text.
  • Manual: configure a valid s3://bucket/key.png — signed URL renders.
  • Manual: configure a regular https:// URL — renders unchanged.
  • Manual: open the editor with a previously-saved malformed s3:// value — catalog header gracefully falls back to the Quilt logo (no white screen, Sentry report fires); editor flags the value once the field is modified.

Greptile Summary

This PR adds two complementary layers of resilience for malformed S3 logo URLs: a runtime fallback in <Logo> that catches parse failures (including bucket-only URLs) and gracefully renders the default Quilt logo with a Sentry report, and a new validators.logoUrl that rejects no-hostname and no-key S3 URLs at form-input time.

  • CustomLogo now wraps parseS3Url in a try/catch and checks for an empty key, returning a discriminated-union result; a useEffect reports errors to Sentry exactly once per distinct bad src.
  • validators.logoUrl replaces the old url/s3Url pair with a single validator that uses new window.URL(v).hostname to reject empty-host URLs (e.g. s3://) and additionally rejects s3://bucket and s3://bucket/ where no key is present.
  • ThemeEditor switches error display to meta.modified || meta.submitFailed and introduces a Preview sub-component that gates the broken-image icon through the same error flag as helperText, keeping both UI signals in sync.

Confidence Score: 5/5

Safe to merge — all changed paths have defence-in-depth (validator + runtime fallback) and are covered by new tests.

The change is well-scoped: it adds a try/catch + key-guard to one render function, a single new validator, and tightens one form field's error-display logic. Both the runtime fallback and the form-side validation are exercised by new tests covering the specific edge cases that triggered the original crash. No existing behaviour is removed in a way that could introduce regressions.

No files require special attention.

Important Files Changed

Filename Overview
catalog/app/components/Logo/index.tsx Adds resilient error handling in CustomLogo: catches parseS3Url exceptions and rejects S3 URLs with empty keys, falling back to QuiltLogo and reporting to Sentry via a useEffect; uses a discriminated union type for type-safe branching.
catalog/app/utils/validators.js Replaces the separate url/s3Url validators with a single logoUrl that checks hostname presence and requires a non-empty S3 key; correctly handles edge cases including s3://, s3://bucket, and s3://bucket/.
catalog/app/containers/Admin/Settings/ThemeEditor.tsx Extracts a Preview sub-component that gates the broken_image icon through the same error flag as the helperText; changes error display from submitFailed-only to modified
catalog/app/components/Logo/index.spec.tsx Adds four new tests covering S3 logo signing, malformed s3:// fallback, bucket-only fallback, and Sentry reporting; mocks @sentry/react cleanly with a beforeEach reset.
catalog/app/utils/validators.spec.js Adds a thorough logoUrl test suite covering empty values, valid URLs, malformed inputs, hostname-less URLs, and S3 URLs without keys.

Reviews (3): Last reviewed commit: "refactor(catalog): model <Logo> parse re..." | Re-trigger Greptile

fiskus added 3 commits May 7, 2026 16:09
`CustomLogo` called `parseS3Url` during render, so any string that
starts with `s3://` but has no hostname (e.g. `s3://`, `s3:///foo`)
threw `bucket not specified` out of the render path and unmounted the
tree — crashing every page that mounts NavBar/footer when such a URL
was persisted to catalog settings.

Add a non-throwing `tryParseS3Url` helper and use it in `<CustomLogo>`.
On parse failure, fall back to the default Quilt logo and report the
bad src to Sentry once via `useEffect`. `parseS3Url` itself keeps its
throwing contract for other callers.
Before the page-level crash was fixed, typing `s3://` into the
ThemeEditor "Logo URL" field would unmount the editor. Now that
`<Logo>` no longer throws, surface the validation feedback in the
form itself:

- Add `validators.s3Url` to the `composeOr` chain so `s3://bucket/key`
  is accepted alongside HTTP(S) URLs and uploaded files.
- Show validation errors as soon as the field is modified (not only
  after a failed submit), so the helperText explains the problem
  while the user is still typing.
- Render a `broken_image` icon in the preview area when the current
  string value fails validation, instead of forwarding it to
  `<Logo>`. This keeps the preview honest during typing and avoids
  spamming Sentry from `<Logo>`'s runtime fallback path on every
  keystroke.
…lformed-s3-url

# Conflicts:
#	catalog/app/containers/Admin/Settings/ThemeEditor.tsx
@codecov
Copy link
Copy Markdown

codecov Bot commented May 7, 2026

Codecov Report

❌ Patch coverage is 91.66667% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 46.55%. Comparing base (dbfbaa0) to head (0ee1dc1).

Files with missing lines Patch % Lines
catalog/app/components/Logo/index.tsx 89.47% 2 Missing ⚠️
...alog/app/containers/Admin/Settings/ThemeEditor.tsx 91.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4881      +/-   ##
==========================================
+ Coverage   46.51%   46.55%   +0.04%     
==========================================
  Files         832      832              
  Lines       34116    34131      +15     
  Branches     5826     5833       +7     
==========================================
+ Hits        15868    15890      +22     
+ Misses      16243    16239       -4     
+ Partials     2005     2002       -3     
Flag Coverage Δ
api-python 93.14% <ø> (ø)
catalog 21.56% <91.66%> (+0.08%) ⬆️
lambda 96.63% <ø> (ø)
py-shared 98.18% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@fiskus fiskus marked this pull request as ready for review May 7, 2026 14:50
Comment thread catalog/app/containers/Admin/Settings/ThemeEditor.tsx
Comment thread catalog/app/containers/Admin/Settings/ThemeEditor.tsx Outdated
@fiskus
Copy link
Copy Markdown
Member Author

fiskus commented May 7, 2026

@greptileai please update confidence score

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 7, 2026

Fair point. The defensive-fallback rationale is sound — keeping errors.url pre-positioned for when composeOr's "last validator wins" behavior gets fixed is a reasonable future-proofing tradeoff. Lowering confidence on this one.

Updated assessment: Low confidence / P3 at most. The dead-key observation is technically correct today, but the developer's intent (avoid a later "where did that message go?" regression when composeOr semantics improve) is a legitimate reason to keep it. Not worth blocking the PR.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

<Logo> already passes blob/http URLs through to a plain <img> (only
s3:// gets signed), so routing the File-value preview through it keeps
behavior consistent with the string-URL branch and lets the spec keep
querying the mocked Logo by testid.
@fiskus
Copy link
Copy Markdown
Member Author

fiskus commented May 7, 2026

@greptileai please update confidence score

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 7, 2026

That's a reasonable forward-compatibility argument. The url key is a reserved message slot for when composeOr is eventually improved to surface the most-specific error rather than always the last one. Lowering confidence — this is intentional defensive code, not dead code.

Updated confidence: Low — the comment is technically accurate about the current runtime behavior, but the intent to keep errors.url as a cheap placeholder for a future composeOr fix is valid. Not worth blocking on.

@fiskus
Copy link
Copy Markdown
Member Author

fiskus commented May 7, 2026

@greptileai please re-review

Comment thread catalog/app/utils/validators.js Outdated
s3://bucket (no key) was passing the URL validator (hostname=bucket) and
then parseS3Url returns key='', so the AWS signer was called with an
empty key and the browser rendered a broken image with no fallback.

Two layers:

- validators.logoUrl: replaces use of validators.url in the theme editor;
  rejects URLs without a hostname AND s3:// URLs without a key.
- <Logo>: treats parseS3Url results with empty key as malformed, falling
  back to QuiltLogo + Sentry. Protects NavBar / persisted settings from
  pre-existing bad data, since the validator only runs on form input.
@fiskus
Copy link
Copy Markdown
Member Author

fiskus commented May 7, 2026

@greptileai please update confidence score

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 7, 2026

The PR has been updated since the initial review. The previously identified gap — s3://bucket (bucket-only, no key) silently passing validation — has been fully addressed:

  1. validators.js: url and s3Url are now replaced by a unified logoUrl validator that explicitly rejects both empty-hostname URLs (s3://) and keyless S3 URLs (s3://bucket, s3://bucket/).
  2. Logo/index.tsx: The useMemo now also returns null when parsed.key is falsy, so the Sentry fallback fires for bucket-only URLs too.
  3. Tests: Both validators.spec.js and Logo/index.spec.tsx have explicit test cases covering the s3://bucket case.

The original concern is fully resolved. Updated confidence score:

Confidence Score: 5/5

All three layers — runtime fallback (Logo), proactive validation (logoUrl), and UX feedback (ThemeEditor) — are consistent and well-tested. Safe to merge.

Replace the null sentinel for malformed S3 URLs with a tagged union
{ _tag: 'ok' } | { _tag: 'error' }. The error variant carries the
real caught Error and the src that produced it, so Sentry reports the
actual cause instead of a synthetic message and the useEffect can
depend on parsedSrc alone. Render branch uses switch + assertNever.

Also narrow CustomLogo's prop type to src: string (parent already gates
on truthy src) and drop the unreachable !src defensive check.
@fiskus
Copy link
Copy Markdown
Member Author

fiskus commented May 8, 2026

@greptileai please re-review

@fiskus fiskus requested a review from drernie May 8, 2026 11:06
@fiskus fiskus enabled auto-merge May 8, 2026 11:06
@fiskus fiskus added this pull request to the merge queue May 8, 2026
Merged via the queue into master with commit 91b30b4 May 8, 2026
47 checks passed
@fiskus fiskus deleted the fix-catalog-logo-malformed-s3-url branch May 8, 2026 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants