Skip to content

[7418] AWSFileUpload control#4827

Open
jvega190 wants to merge 5 commits intocraftercms:developfrom
jvega190:feature/7418-aws-file-upload
Open

[7418] AWSFileUpload control#4827
jvega190 wants to merge 5 commits intocraftercms:developfrom
jvega190:feature/7418-aws-file-upload

Conversation

@jvega190
Copy link
Copy Markdown
Member

@jvega190 jvega190 commented Mar 19, 2026

craftercms/craftercms#7418

Summary by CodeRabbit

  • New Features

    • Added an AWS S3 file upload control for forms with integrated preview for images, videos, documents, and other assets.
  • Improvements

    • Form value handling for the new AWS file control now extracts and serializes values correctly for saving.
    • Upload component adds selectable HTTP method, optional file details, configurable progress bar visibility, and a disabled state.

@jvega190 jvega190 changed the title [7418 AWSFileUpload control [7418] AWSFileUpload control Mar 19, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 19, 2026

Walkthrough

Adds a new Forms Engine control component AwsFileUpload for S3 uploads and previews, wires it into the Forms Engine (control map, value retriever, serializer), and extends SingleFileUpload with method, detail/progress visibility, and disabled handling.

Changes

Cohort / File(s) Summary
New AWS File Upload Control
ui/app/src/components/FormsEngine/controls/AWSFileUpload.tsx
New React control AwsFileUpload that wraps SingleFileUpload, posts to the S3 upload endpoint, converts responses to AwsFile (key, bucket, url), calls setValue, and renders location and previews (image/video/iframe/placeholder). Exports AwsFile and AwsFileUploadProps.
Forms Engine wiring
ui/app/src/components/FormsEngine/lib/controlMap.ts, ui/app/src/components/FormsEngine/lib/valueRetrievers.ts, ui/app/src/components/FormsEngine/lib/valueSerializers.ts
Registers 'aws-file-upload' as a lazy-loaded control, adds awsFileExtractor (returns first array item) to valueRetrieverLookup, and adds a serializer (prepareAwsFile) to valueSerializersLookup. Also tightened optional access in prepareString.
SingleFileUpload updates
ui/app/src/components/SingleFileUpload/SingleFileUpload.tsx
Extended SingleFileUploadProps with optional method, showFileDetails, showProgressBar, disabled. Uppy XHRUpload uses method; progress and file details honor new flags; input/button respect disabled; removed an unused import.

Sequence Diagram

sequenceDiagram
    autonumber
    actor User
    participant AwsFileUpload as AwsFileUpload<br/>(Component)
    participant SingleFileUpload as SingleFileUpload<br/>(Component)
    participant Uppy as Uppy/XHRUpload
    participant S3API as S3 Upload API
    participant FormEngine as Forms Engine

    User->>AwsFileUpload: Open form / select file
    AwsFileUpload->>SingleFileUpload: provide endpoint, siteId/profileId, props, callbacks
    User->>SingleFileUpload: Click "Choose File" / confirm
    SingleFileUpload->>Uppy: enqueue file & start upload (PUT/POST)
    Uppy->>S3API: Upload file
    S3API-->>Uppy: Return upload response (item list)
    Uppy-->>SingleFileUpload: onComplete
    SingleFileUpload-->>AwsFileUpload: invoke onComplete handler with result
    AwsFileUpload->>AwsFileUpload: parse first successful item -> AwsFile{key,bucket,url}
    AwsFileUpload->>FormEngine: setValue(AwsFile)
    FormEngine-->>AwsFileUpload: new value prop
    AwsFileUpload->>User: render s3:// location and preview (image/video/asset)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Possibly related PRs

Suggested reviewers

  • rart
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description only contains a GitHub issue link with no additional context, context of changes, or how the implementation addresses the issue. Expand the description to explain what the AWSFileUpload control does, how it integrates with the Forms Engine, and how it addresses the referenced issue.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically identifies the main change: implementation of the AWSFileUpload control component, which is the primary focus of all modifications in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
ui/app/src/components/SingleFileUpload/SingleFileUpload.tsx (1)

411-428: Empty Typography rendered when file is null.

When showFileDetails is true but no file is selected, an empty Typography element with mb: 2 is rendered, causing unnecessary margin. Consider moving the file check outside.

♻️ Suggested improvement
-			{showFileDetails && (
-				<Typography variant="subtitle1" component="h2" sx={{ mb: 2 }}>
-					{file && (
-						<Box
-							component="em"
-							className={`single-file-upload--filename ${fileNameErrorClass}`}
-							sx={{
-								overflow: 'hidden',
-								textOverflow: 'ellipsis',
-								whiteSpace: 'nowrap'
-							}}
-							title={file.name}
-						>
-							{file.name}
-						</Box>
-					)}
-				</Typography>
-			)}
+			{showFileDetails && file && (
+				<Typography variant="subtitle1" component="h2" sx={{ mb: 2 }}>
+					<Box
+						component="em"
+						className={`single-file-upload--filename ${fileNameErrorClass}`}
+						sx={{
+							overflow: 'hidden',
+							textOverflow: 'ellipsis',
+							whiteSpace: 'nowrap'
+						}}
+						title={file.name}
+					>
+						{file.name}
+					</Box>
+				</Typography>
+			)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/src/components/SingleFileUpload/SingleFileUpload.tsx` around lines 411
- 428, When showFileDetails is true but file is null the code still renders an
empty Typography (mb: 2); change the condition so Typography is only rendered
when both showFileDetails and file are truthy (i.e., move the file check to wrap
the Typography element), keeping the inner Box (single-file-upload--filename)
and its title/file.name logic intact; update references in SingleFileUpload.tsx
around showFileDetails and file so no empty Typography is produced.
ui/app/src/components/FormsEngine/controls/AWSFileUpload.tsx (1)

124-133: Consider sandbox attribute for iframe security.

While the URL comes from a trusted S3 response, in security-sensitive environments consider adding a sandbox attribute (e.g., sandbox="allow-same-origin") to restrict iframe capabilities. Note that this may affect PDF rendering in some browsers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/src/components/FormsEngine/controls/AWSFileUpload.tsx` around lines
124 - 133, Add a sandbox attribute to the iframe created in AWSFileUpload.tsx
(the Box with component="iframe" used when fileType === 'asset' and
src={value.url}) to limit iframe capabilities; set sandbox to an appropriate
value such as "allow-same-origin" (or "allow-same-origin allow-scripts" if
needed for rendering) and document the caveat that this may affect PDF rendering
in some browsers so test after change.
ui/app/src/components/FormsEngine/lib/valueRetrievers.ts (1)

233-235: Add type annotations for consistency and safety.

The function lacks parameter and return type annotations. Consider adding them for consistency with other typed extractors and to clarify that the return value may be undefined when the array is empty.

♻️ Suggested type annotations
-export function awsFileExtractor(value) {
-	return arrayFieldExtractor(value)[0];
+export function awsFileExtractor(value: unknown): unknown {
+	return arrayFieldExtractor(value)[0];
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/src/components/FormsEngine/lib/valueRetrievers.ts` around lines 233 -
235, Add TypeScript annotations to awsFileExtractor: annotate the parameter type
to match the input expected by arrayFieldExtractor (e.g., the same type used by
other extractors) and set the return type to include undefined (e.g., FileType |
undefined) to reflect that arrayFieldExtractor(value)[0] may be absent; ensure
the function signature aligns with arrayFieldExtractor's parameter type and the
project’s File/Attachment type names.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/app/src/components/FormsEngine/controls/AWSFileUpload.tsx`:
- Around line 63-67: The bucket field in the AwsFile construction (awsFile:
AwsFile) can become "bucketName/undefined" when item.bucketName is truthy but
item.prefix is nullish; update the bucket assembly to only append the prefix
when item.prefix is non-null/defined and non-empty (e.g., check item.prefix !==
undefined && item.prefix !== null && item.prefix !== '' or use a truthy check)
so the bucket becomes either "bucketName/prefix" or just "bucketName" (and still
fall back to item.bucket ?? '' when item.bucketName is falsy); modify the code
that builds the bucket string near where awsFile is created to include that
conditional.
- Around line 121-123: In AWSFileUpload.tsx the video source type is built from
value.url.match(/\.(.+)$/)?.[1] which can be undefined and yield
"video/undefined"; update the logic that generates the MIME subtype for the
<source> element (where value.url is used) to safely extract the extension and
fall back to a sensible default (e.g., "mp4") or map common extensions to proper
MIME subtypes before building `video/...`, so the type becomes
`video/<ext-or-fallback>` instead of possibly `video/undefined`.
- Around line 82-98: AWSFileUpload renders a parent <form
id="asset_upload_form"> and also uses <SingleFileUpload>, but SingleFileUpload
itself renders another <form id="asset_upload_form"> (see SingleFileUpload JSX
around line 378), causing nested forms and duplicate IDs; fix by removing the
inner form wrapper from SingleFileUpload so it no longer emits a <form> (keep
only the input/button elements and attach handlers like onComplete/disabled to
those elements) and rely on the parent AWSFileUpload form, or if you must keep a
form in SingleFileUpload, rename its id and add a prop (e.g., formTarget/formId)
to pass a unique id from AWSFileUpload to avoid duplication and nesting (update
SingleFileUpload’s interface and usages accordingly).

---

Nitpick comments:
In `@ui/app/src/components/FormsEngine/controls/AWSFileUpload.tsx`:
- Around line 124-133: Add a sandbox attribute to the iframe created in
AWSFileUpload.tsx (the Box with component="iframe" used when fileType ===
'asset' and src={value.url}) to limit iframe capabilities; set sandbox to an
appropriate value such as "allow-same-origin" (or "allow-same-origin
allow-scripts" if needed for rendering) and document the caveat that this may
affect PDF rendering in some browsers so test after change.

In `@ui/app/src/components/FormsEngine/lib/valueRetrievers.ts`:
- Around line 233-235: Add TypeScript annotations to awsFileExtractor: annotate
the parameter type to match the input expected by arrayFieldExtractor (e.g., the
same type used by other extractors) and set the return type to include undefined
(e.g., FileType | undefined) to reflect that arrayFieldExtractor(value)[0] may
be absent; ensure the function signature aligns with arrayFieldExtractor's
parameter type and the project’s File/Attachment type names.

In `@ui/app/src/components/SingleFileUpload/SingleFileUpload.tsx`:
- Around line 411-428: When showFileDetails is true but file is null the code
still renders an empty Typography (mb: 2); change the condition so Typography is
only rendered when both showFileDetails and file are truthy (i.e., move the file
check to wrap the Typography element), keeping the inner Box
(single-file-upload--filename) and its title/file.name logic intact; update
references in SingleFileUpload.tsx around showFileDetails and file so no empty
Typography is produced.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 17acf4b8-1a62-46b7-b948-cefbae949cdf

📥 Commits

Reviewing files that changed from the base of the PR and between 2014a45 and 1df2ea2.

📒 Files selected for processing (5)
  • ui/app/src/components/FormsEngine/controls/AWSFileUpload.tsx
  • ui/app/src/components/FormsEngine/lib/controlMap.ts
  • ui/app/src/components/FormsEngine/lib/valueRetrievers.ts
  • ui/app/src/components/FormsEngine/lib/valueSerializers.ts
  • ui/app/src/components/SingleFileUpload/SingleFileUpload.tsx

Comment thread ui/app/src/components/FormsEngine/controls/AWSFileUpload.tsx
Comment thread ui/app/src/components/FormsEngine/controls/AWSFileUpload.tsx
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/app/src/components/FormsEngine/controls/AWSFileUpload.tsx`:
- Around line 147-153: The getAwsFileType function's regexes fail on signed S3
URLs because they require the extension at end-of-string; update the three
patterns in getAwsFileType to allow an optional query string after the extension
(e.g., make them match extension followed by either ? or end-of-string) so URLs
like file.mp4?X-Amz-Signature=... are correctly detected as 'video'; adjust the
regexes for 'image', 'video', and 'asset' accordingly while keeping
case-insensitive matching.
- Around line 121-123: The current MIME type extraction in AWSFileUpload.tsx
(where <source src={value.url} type={`video/${value.url.match(/\.(.+)$/)?.[1] ??
'mp4'}`} /> is used) can capture query strings and fragments; replace the regex
with one that only captures the file extension before any ? or # (e.g.,
/\.([A-Za-z0-9]+)(?:[?#].*)?$/) and use that capture for the MIME subtype
(falling back to 'mp4' if no match) so URLs like video.mp4?token=... produce
'video/mp4' instead of 'video/mp4?token=...'.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3bf6f0e2-805d-4180-8485-fa6fc2aeb6aa

📥 Commits

Reviewing files that changed from the base of the PR and between 1df2ea2 and f3755e2.

📒 Files selected for processing (1)
  • ui/app/src/components/FormsEngine/controls/AWSFileUpload.tsx

Comment thread ui/app/src/components/FormsEngine/controls/AWSFileUpload.tsx
Comment thread ui/app/src/components/FormsEngine/controls/AWSFileUpload.tsx
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
ui/app/src/components/FormsEngine/lib/valueSerializers.ts (1)

42-42: Minor: prepareAwsFile duplicates prepareArray.

Both functions return { item: value }. If you keep the single-object wrapping as-is, you can drop prepareAwsFile and point this entry at prepareArray to reduce duplication. (If you adopt the array-wrap fix suggested on lines 220–222, this becomes moot.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/src/components/FormsEngine/lib/valueSerializers.ts` at line 42, The
'aws-file-upload' serializer currently calls prepareAwsFile which duplicates
prepareArray by returning { item: value }; remove that duplication by changing
the mapping for the 'aws-file-upload' key to use prepareArray instead of
prepareAwsFile and delete the now-unused prepareAwsFile function (or keep it
only if referenced elsewhere). Update any imports/exports if prepareAwsFile was
exported so there are no unused symbols remaining.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@ui/app/src/components/FormsEngine/lib/valueSerializers.ts`:
- Line 42: The 'aws-file-upload' serializer currently calls prepareAwsFile which
duplicates prepareArray by returning { item: value }; remove that duplication by
changing the mapping for the 'aws-file-upload' key to use prepareArray instead
of prepareAwsFile and delete the now-unused prepareAwsFile function (or keep it
only if referenced elsewhere). Update any imports/exports if prepareAwsFile was
exported so there are no unused symbols remaining.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7c6bfba4-2233-467e-809b-2cf43459ab2a

📥 Commits

Reviewing files that changed from the base of the PR and between f3755e2 and 4e8cbf7.

📒 Files selected for processing (2)
  • ui/app/src/components/FormsEngine/controls/AWSFileUpload.tsx
  • ui/app/src/components/FormsEngine/lib/valueSerializers.ts
✅ Files skipped from review due to trivial changes (1)
  • ui/app/src/components/FormsEngine/controls/AWSFileUpload.tsx

@jvega190 jvega190 marked this pull request as ready for review April 20, 2026 18:47
@jvega190 jvega190 requested a review from rart as a code owner April 20, 2026 18:47
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