Skip to content

refactor(catalog): add bucket context and S3 config factory#4924

Open
drernie wants to merge 1 commit into
masterfrom
prefactor/bucket-seams
Open

refactor(catalog): add bucket context and S3 config factory#4924
drernie wants to merge 1 commit into
masterfrom
prefactor/bucket-seams

Conversation

@drernie
Copy link
Copy Markdown
Member

@drernie drernie commented May 28, 2026

Current pain

Bucket-route components repeatedly read useParams().bucket, and S3 clients are created without a single bucket-config-aware path. That makes region/presign behavior easy to drift as more bucket-specific configuration is introduced.

Changes

  • Add BucketContextProvider and useBucketContext() for the bucket route subtree.
  • Mount the provider at containers/Bucket/Bucket.tsx, reusing existing bucket existence/cache plumbing without adding a fetch.
  • Replace direct bucket param reads under containers/Bucket/* with useBucketContext() where they read the route bucket.
  • Extend the existing useS3Factory() / useS3() path to accept bucket config and preserve the old region-string/default behavior.
  • Route bucket read-site S3 consumers through useBucketContext().config.
  • Add hook/provider tests and update direct hook tests to mount the provider.

Bucket context inventory

Direct bucket route reads were replaced in overview, dir/file, package tree/revisions/compare, queries, workflows, and package-dialog/file-picker paths. Bucket.tsx remains the route owner and provider mount point.

S3 construction inventory

No separate S3 factory was added. Existing utils/AWS/S3.jsx remains the construction point; bucket subtree AWS.S3.use() call sites now pass bucket context config.

Verification

  • npm run lint
  • npm run test:only -- app/containers/Bucket/context.spec.tsx app/containers/Bucket/Queries/Athena/model/state.spec.tsx app/containers/Bucket/PackageDialog/State/schema.spec.ts app/containers/Bucket/PackageDialog/State/params.spec.ts app/containers/Bucket/Dir/Toolbar/CreatePackage/useSuccessors.spec.ts --no-cache — 37 passed
  • NODE_OPTIONS="--localstorage-file=/tmp/quilt-catalog-vitest-localstorage.json" npm test currently fails in unrelated suites under Node 26/npm 11:
    • app/utils/spreadsheets/spreadsheets.spec.ts: date expectations off by one day for ODS/FODS/CSV cases
    • app/containers/Bucket/Queries/Athena/model/requests.spec.ts: two useQueryRun async timing tests time out

Pre-merge stack verification

Not run yet. Before merge: workflow-dispatch an interim catalog image for this branch, pin the immutable tag on the shared test stack, and record the test-stack URL/image tag here.

Greptile Summary

This PR centralizes bucket route reads into a BucketContextProvider / useBucketContext() hook pair, replacing scattered useParams().bucket calls across the bucket subtree and routing all S3 client construction through a bucket-config-aware useS3(bucketConfig) path.

  • New context module (context.tsx): introduces BucketContextProvider (auto-reads :bucket from route params or accepts explicit bucket + config props) and useBucketContext() consumed by ~20 files previously doing direct useParams reads.
  • S3 factory extension (S3.jsx): useS3 / the internal factory now accept a BucketConfig object (or legacy region string) so that bucket-specific region is forwarded to the cached client; backward-compatible with callers that pass nothing.
  • Double-provider in Bucket.tsx: an outer route-based provider lets BucketInner call useBucketStrict(), while a separate inner provider (mounted after useBucketExistence resolves) supplies the cached region config to the actual page components.

Confidence Score: 4/5

The refactor is mechanically correct and well-tested; the two-provider nesting in Bucket.tsx is a design choice that works today but leaves a latent trap for future components added in the wrong layer.

The migration is broad (30+ files) but each change is a straightforward substitution. The main fragility is that config = {} in BucketContextProvider creates a new object reference on every render, defeating the useMemo in BucketContextValueProvider and causing unnecessary context re-renders for all consumers of the outer provider. The double-provider pattern is functional but requires contributors to understand which layer they're adding code to.

catalog/app/containers/Bucket/context.tsx (config default stability) and catalog/app/containers/Bucket/Bucket.tsx (two-layer provider structure).

Important Files Changed

Filename Overview
catalog/app/containers/Bucket/context.tsx New file: implements BucketContextProvider and useBucketContext; the config = {} default creates an unstable object reference on each render, defeating the useMemo inside BucketContextValueProvider.
catalog/app/containers/Bucket/Bucket.tsx Mounts two nested BucketContextProviders: an outer one (no config) for BucketInner and an inner region-aware one inside BucketReady; components between the two layers silently get the empty-config provider.
catalog/app/utils/AWS/S3.jsx Extended useS3/useS3Factory to accept bucketConfig (string
catalog/app/containers/Bucket/Routes.ts useBucketSafe now delegates to useBucketContext().name instead of useParams; contract changes from returning undefined to throwing if used outside the provider, but the only call site is useBucketStrict which re-validated anyway.
catalog/app/containers/Bucket/context.spec.tsx New test file covering route-based and explicit-prop provider paths; covers the happy paths but lacks a test for the useBucketContext error when no provider is present.
catalog/app/containers/Bucket/Queries/Athena/model/state.tsx Replaces direct useParams bucket read with useBucketContext; removes invariant that is now enforced by the provider.
catalog/app/containers/Bucket/Dir.tsx Replaced useParams bucket read with useBucketContext and passes bucket config to AWS.S3.use(); straightforward migration.

Comments Outside Diff (1)

  1. catalog/app/utils/AWS/S3.jsx, line 154-156 (link)

    P2 The JSDoc comment for useS3Factory still documents the old (region?) => S3Client signature. The factory returned now accepts a bucketConfig argument that can be a region string, a BucketConfig object, or undefined, so the comment should be updated to avoid misleading callers who consult it.

Reviews (1): Last reviewed commit: "refactor(catalog): add bucket context an..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

Introduce a route-level bucket context for repeated bucket param reads and thread bucket config into the existing S3 factory. The motivation is repeated route parsing and region/presign inconsistency risk across bucket read paths.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

❌ Patch coverage is 25.51020% with 73 lines in your changes missing coverage. Please review.
✅ Project coverage is 46.54%. Comparing base (a1fda19) to head (c87027d).
⚠️ Report is 9 commits behind head on master.

Files with missing lines Patch % Lines
catalog/app/containers/Bucket/Bucket.tsx 0.00% 8 Missing ⚠️
catalog/app/containers/Bucket/File/File.jsx 0.00% 8 Missing ⚠️
catalog/app/utils/AWS/S3.jsx 0.00% 6 Missing and 2 partials ⚠️
catalog/app/containers/Bucket/Dir.tsx 0.00% 3 Missing and 1 partial ⚠️
catalog/app/containers/Bucket/Meta.tsx 0.00% 4 Missing ⚠️
catalog/app/containers/Bucket/Workflows/index.tsx 0.00% 4 Missing ⚠️
...og/app/containers/Bucket/requests/bucketListing.ts 0.00% 4 Missing ⚠️
...atalog/app/containers/Bucket/Overview/Overview.tsx 0.00% 3 Missing ⚠️
.../app/containers/Bucket/PackageTree/PackageTree.tsx 0.00% 3 Missing ⚠️
catalog/app/containers/Bucket/FallbackToDir.tsx 0.00% 2 Missing ⚠️
... and 15 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4924      +/-   ##
==========================================
- Coverage   46.55%   46.54%   -0.01%     
==========================================
  Files         832      833       +1     
  Lines       34120    34168      +48     
  Branches     5833     5839       +6     
==========================================
+ Hits        15884    15904      +20     
- Misses      16237    16264      +27     
- Partials     1999     2000       +1     
Flag Coverage Δ
api-python 93.14% <ø> (ø)
catalog 21.59% <25.51%> (+0.04%) ⬆️
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.


export function BucketContextProvider({
bucket: bucketProp,
config = {},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unstable default reference triggers unnecessary context re-renders

config = {} creates a brand-new object literal on every call to BucketContextProvider. That reference then flows into BucketContextValueProvider's useMemo([name, config]) dependency array, so the memoized value is recreated on every render even when neither the bucket name nor the config has actually changed. Every consumer of the outer context (e.g. the BucketInner subtree in Bucket.tsx, test wrappers that omit config) will re-render unnecessarily on each parent re-render.

Replace the inline default with a stable module-level constant: const EMPTY_CONFIG: BucketConfig = {}, and use that as the default value.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines 86 to +91
export default function Bucket() {
return (
<BucketContextProvider>
<BucketInner />
</BucketContextProvider>
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Outer provider supplies config: {} to components that will later get the region-aware inner provider

Bucket() mounts an outer BucketContextProvider (no props → config = {}) so that BucketInner can call useBucketStrict(). Then, only after useBucketExistence resolves Ok, BucketReady mounts an inner BucketContextProvider with the cached region. Components that sit between the two providers — currently BucketPreferences.Provider, MetaTitle, and BucketNav.BucketNav — would receive the empty-config outer context if they ever call useBucketContext().config for S3 construction. None of them do today, but the silent divergence between the two context layers is easy to miss when adding new features inside BucketLayout but outside the Ok branch.

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.

1 participant