fix: bulk upload validation when files are missing (#11744)

### What?

This PR ensures that bulk uploads fail if any file is missing, rather
than skipping missing files and proceeding with the upload.

### Why?

This fixes unintended behavior where missing files were skipped,
allowing partial uploads when they shouldn't be allowed.

### How?

- Prevents submission if any file is missing by checking `req.status ===
400`.
- Updates `FileSidebar` to correctly handle cases where a file is
`null`.
This commit is contained in:
Patrik
2025-03-18 10:26:17 -04:00
committed by GitHub
parent 0fe922e214
commit ea66e2167c
43 changed files with 116 additions and 7 deletions

View File

@@ -85,6 +85,7 @@ export const clientTranslationKeys = createClientTranslationKeys([
'error:usernameAlreadyRegistered',
'error:tokenNotProvided',
'error:unPublishingDocument',
'error:problemUploadingFile',
'fields:addLabel',
'fields:addLink',
@@ -334,6 +335,7 @@ export const clientTranslationKeys = createClientTranslationKeys([
'upload:width',
'upload:fileName',
'upload:fileSize',
'upload:noFile',
'validation:emailAddress',
'validation:enterNumber',

View File

@@ -392,6 +392,7 @@ export const arTranslations: DefaultTranslationsObject = {
height: 'الطّول',
lessInfo: 'معلومات أقلّ',
moreInfo: 'معلومات أكثر',
noFile: 'لا يوجد ملف',
pasteURL: 'لصق الرابط',
previewSizes: 'أحجام المعاينة',
selectCollectionToBrowse: 'حدّد مجموعة لاستعراضها',

View File

@@ -399,6 +399,7 @@ export const azTranslations: DefaultTranslationsObject = {
height: 'Hündürlük',
lessInfo: 'Daha az məlumat',
moreInfo: 'Daha çox məlumat',
noFile: 'Heç bir fayl',
pasteURL: 'URL yapışdır',
previewSizes: 'Öncədən baxış ölçüləri',
selectCollectionToBrowse: 'Gözdən keçirmək üçün bir Kolleksiya seçin',

View File

@@ -397,6 +397,7 @@ export const bgTranslations: DefaultTranslationsObject = {
height: 'Височина',
lessInfo: 'По-малко информация',
moreInfo: 'Повече информация',
noFile: 'Няма файл',
pasteURL: 'Поставяне на URL',
previewSizes: 'Преглед на размери',
selectCollectionToBrowse: 'Избери колекция, която да разгледаш',

View File

@@ -398,6 +398,7 @@ export const caTranslations: DefaultTranslationsObject = {
height: 'Alçada',
lessInfo: 'Menys informació',
moreInfo: 'Més informació',
noFile: 'No hi ha cap fitxer',
pasteURL: "Enganxa l'URL",
previewSizes: 'Mides de la vista prèvia',
selectCollectionToBrowse: 'Selecciona una col·lecció per explorar',

View File

@@ -395,6 +395,7 @@ export const csTranslations: DefaultTranslationsObject = {
height: 'Výška',
lessInfo: 'Méně informací',
moreInfo: 'Více informací',
noFile: 'Žádný soubor',
pasteURL: 'Vložit URL',
previewSizes: 'Náhled velikostí',
selectCollectionToBrowse: 'Vyberte kolekci pro procházení',

View File

@@ -396,6 +396,7 @@ export const daTranslations: DefaultTranslationsObject = {
height: 'Højde',
lessInfo: 'Mindre info',
moreInfo: 'Mere info',
noFile: 'Ingen fil',
pasteURL: 'Indsæt URL',
previewSizes: 'Forhåndsvisningsstørrelser',
selectCollectionToBrowse: 'Vælg en samling for at browse',

View File

@@ -403,6 +403,7 @@ export const deTranslations: DefaultTranslationsObject = {
height: 'Höhe',
lessInfo: 'Weniger Info',
moreInfo: 'Mehr Info',
noFile: 'Keine Datei',
pasteURL: 'URL einfügen',
previewSizes: 'Vorschaugrößen',
selectCollectionToBrowse: 'Wähle eine Sammlung zum Durchsuchen aus',

View File

@@ -398,6 +398,7 @@ export const enTranslations = {
height: 'Height',
lessInfo: 'Less info',
moreInfo: 'More info',
noFile: 'No file',
pasteURL: 'Paste URL',
previewSizes: 'Preview Sizes',
selectCollectionToBrowse: 'Select a Collection to Browse',

View File

@@ -402,6 +402,7 @@ export const esTranslations: DefaultTranslationsObject = {
height: 'Alto',
lessInfo: 'Menos info',
moreInfo: 'Más info',
noFile: 'Ningún archivo',
pasteURL: 'Pegar URL',
previewSizes: 'Tamaños de Vista Previa',
selectCollectionToBrowse: 'Selecciona una Colección',

View File

@@ -393,6 +393,7 @@ export const etTranslations: DefaultTranslationsObject = {
height: 'Kõrgus',
lessInfo: 'Vähem infot',
moreInfo: 'Rohkem infot',
noFile: 'Pole faili',
pasteURL: 'Kleebi URL',
previewSizes: 'Eelvaate suurused',
selectCollectionToBrowse: 'Vali kollektsioon sirvimiseks',

View File

@@ -395,6 +395,7 @@ export const faTranslations: DefaultTranslationsObject = {
height: 'ارتفاع',
lessInfo: 'اطلاعات کمتر',
moreInfo: 'اطلاعات بیشتر',
noFile: 'هیچ فایلی',
pasteURL: 'چسباندن آدرس اینترنتی',
previewSizes: 'اندازه های پیش نمایش',
selectCollectionToBrowse: 'یک مجموعه را برای مرور انتخاب کنید',

View File

@@ -407,6 +407,7 @@ export const frTranslations: DefaultTranslationsObject = {
height: 'Hauteur',
lessInfo: 'Moins dinfos',
moreInfo: 'Plus dinfos',
noFile: 'Aucun fichier',
pasteURL: "Coller l'URL",
previewSizes: 'Tailles daperçu',
selectCollectionToBrowse: 'Sélectionnez une collection à parcourir',

View File

@@ -388,6 +388,7 @@ export const heTranslations: DefaultTranslationsObject = {
height: 'גובה',
lessInfo: 'פחות מידע',
moreInfo: 'מידע נוסף',
noFile: 'אין קובץ',
pasteURL: 'הדבק כתובת אתר',
previewSizes: 'גדלי תצוגה מקדימה',
selectCollectionToBrowse: 'בחר אוסף לצפייה',

View File

@@ -397,6 +397,7 @@ export const hrTranslations: DefaultTranslationsObject = {
height: 'Visina',
lessInfo: 'Manje informacija',
moreInfo: 'Više informacija',
noFile: 'Nema datoteke',
pasteURL: 'Zalijepi URL',
previewSizes: 'Veličine pregleda',
selectCollectionToBrowse: 'Odaberite kolekciju za pregled',

View File

@@ -400,6 +400,7 @@ export const huTranslations: DefaultTranslationsObject = {
height: 'Magasság',
lessInfo: 'Kevesebb információ',
moreInfo: 'További információ',
noFile: 'Nincs fájl',
pasteURL: 'URL beillesztése',
previewSizes: 'Előnézeti méretek',
selectCollectionToBrowse: 'Válassza ki a böngészni kívánt gyűjteményt',

View File

@@ -401,6 +401,7 @@ export const itTranslations: DefaultTranslationsObject = {
height: 'Altezza',
lessInfo: 'Meno info',
moreInfo: 'Più info',
noFile: 'Nessun file',
pasteURL: 'Incolla URL',
previewSizes: 'Anteprime Dimensioni',
selectCollectionToBrowse: 'Seleziona una Collezione da Sfogliare',

View File

@@ -396,6 +396,7 @@ export const jaTranslations: DefaultTranslationsObject = {
height: '高さ',
lessInfo: '詳細を隠す',
moreInfo: '詳細を表示',
noFile: 'ファイルなし',
pasteURL: 'URLを貼り付け',
previewSizes: 'プレビューサイズ',
selectCollectionToBrowse: '閲覧するコレクションを選択',

View File

@@ -394,6 +394,7 @@ export const koTranslations: DefaultTranslationsObject = {
height: '높이',
lessInfo: '정보 숨기기',
moreInfo: '정보 더보기',
noFile: '파일 없음',
pasteURL: 'URL 붙여넣기',
previewSizes: '미리보기 크기',
selectCollectionToBrowse: '찾을 컬렉션 선택',

View File

@@ -399,6 +399,7 @@ export const ltTranslations: DefaultTranslationsObject = {
height: 'Aukštis',
lessInfo: 'Mažiau informacijos',
moreInfo: 'Daugiau informacijos',
noFile: 'Nėra failo',
pasteURL: 'Įklijuokite URL',
previewSizes: 'Peržiūros dydžiai',
selectCollectionToBrowse: 'Pasirinkite kolekciją, kurią norėtumėte naršyti',

View File

@@ -402,6 +402,7 @@ export const myTranslations: DefaultTranslationsObject = {
height: 'Height',
lessInfo: 'အချက်အလက်နည်းတယ်။',
moreInfo: 'အချက်အလက်',
noFile: 'Tiada fail',
pasteURL: 'URL ကို ကူးထည့်ပါ',
previewSizes: 'Saiz Pratonton',
selectCollectionToBrowse: 'စုစည်းမှု တစ်ခုခုကို ရွေးချယ်ပါ။',

View File

@@ -398,6 +398,7 @@ export const nbTranslations: DefaultTranslationsObject = {
height: 'Høyde',
lessInfo: 'Mindre info',
moreInfo: 'Mer info',
noFile: 'Ingen fil',
pasteURL: 'Lim inn URL',
previewSizes: 'Forhåndsvisningsstørrelser',
selectCollectionToBrowse: 'Velg en samling å bla i',

View File

@@ -401,6 +401,7 @@ export const nlTranslations: DefaultTranslationsObject = {
height: 'Hoogte',
lessInfo: 'Minder info',
moreInfo: 'Meer info',
noFile: 'Geen bestand',
pasteURL: 'URL plakken',
previewSizes: 'Voorbeeldgroottes',
selectCollectionToBrowse: 'Selecteer een collectie om door te bladeren',

View File

@@ -397,6 +397,7 @@ export const plTranslations: DefaultTranslationsObject = {
height: 'Wysokość',
lessInfo: 'Mniej informacji',
moreInfo: 'Więcej informacji',
noFile: 'Brak pliku',
pasteURL: 'Wklej URL',
previewSizes: 'Rozmiary podglądu',
selectCollectionToBrowse: 'Wybierz kolekcję aby przejrzeć',

View File

@@ -398,6 +398,7 @@ export const ptTranslations: DefaultTranslationsObject = {
height: 'Altura',
lessInfo: 'Ver menos',
moreInfo: 'Ver mais',
noFile: 'Sem arquivo',
pasteURL: 'Colar URL',
previewSizes: 'Tamanhos de Pré-visualização',
selectCollectionToBrowse: 'Selecione uma Coleção para Navegar',

View File

@@ -401,6 +401,7 @@ export const roTranslations: DefaultTranslationsObject = {
height: 'Înălțime',
lessInfo: 'Mai puține informații',
moreInfo: 'Mai multe informații',
noFile: 'Niciun fișier',
pasteURL: 'Lipește URL',
previewSizes: 'Dimensiuni Previzualizare',
selectCollectionToBrowse: 'Selectați o colecție pentru navigare',

View File

@@ -397,6 +397,7 @@ export const rsTranslations: DefaultTranslationsObject = {
height: 'Висина',
lessInfo: 'Мање информација',
moreInfo: 'Више информација',
noFile: 'Nema fajla',
pasteURL: 'Налепи URL',
previewSizes: 'Величине прегледа',
selectCollectionToBrowse: 'Одаберите колекцију за преглед',

View File

@@ -398,6 +398,7 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
height: 'Visina',
lessInfo: 'Manje informacija',
moreInfo: 'Više informacija',
noFile: 'Nema datoteke',
pasteURL: 'Nalepi URL',
previewSizes: 'Veličine pregleda',
selectCollectionToBrowse: 'Odaberite kolekciju za pregled',

View File

@@ -401,6 +401,7 @@ export const ruTranslations: DefaultTranslationsObject = {
height: 'Высота',
lessInfo: 'Меньше информации',
moreInfo: 'Больше информации',
noFile: 'Нет файла',
pasteURL: 'Вставить URL',
previewSizes: 'Предварительный просмотр размеров',
selectCollectionToBrowse: 'Выберите Коллекцию для просмотра',

View File

@@ -398,6 +398,7 @@ export const skTranslations: DefaultTranslationsObject = {
height: 'Výška',
lessInfo: 'Menej informácií',
moreInfo: 'Viac informácií',
noFile: 'Žiadny súbor',
pasteURL: 'Vložiť URL',
previewSizes: 'Náhľady veľkostí',
selectCollectionToBrowse: 'Vyberte kolekciu na prezeranie',

View File

@@ -396,6 +396,7 @@ export const slTranslations: DefaultTranslationsObject = {
height: 'Višina',
lessInfo: 'Manj informacij',
moreInfo: 'Več informacij',
noFile: 'Ni datoteke.',
pasteURL: 'Prilepi URL',
previewSizes: 'Velikosti predogleda',
selectCollectionToBrowse: 'Izberite zbirko za brskanje',

View File

@@ -398,6 +398,7 @@ export const svTranslations: DefaultTranslationsObject = {
height: 'Höjd',
lessInfo: 'Mindre info',
moreInfo: 'Mer info',
noFile: 'Ingen fil',
pasteURL: 'Klistra in URL',
previewSizes: 'Förhandsgranska storlekar',
selectCollectionToBrowse: 'Välj en samling att bläddra i',

View File

@@ -391,6 +391,7 @@ export const thTranslations: DefaultTranslationsObject = {
height: 'ความสูง',
lessInfo: 'ซ่อนข้อมูล',
moreInfo: 'แสดงข้อมูล',
noFile: 'ไม่มีไฟล์',
pasteURL: 'วาง URL',
previewSizes: 'ขนาดตัวอย่าง',
selectCollectionToBrowse: 'เลือก Collection ที่ต้องการค้นหา',

View File

@@ -402,6 +402,7 @@ export const trTranslations: DefaultTranslationsObject = {
height: 'Yükseklik',
lessInfo: 'Daha az bilgi',
moreInfo: 'Daha fazla bilgi',
noFile: 'Dosya yok',
pasteURL: 'URL yapıştır',
previewSizes: 'Önizleme Boyutları',
selectCollectionToBrowse: 'Görüntülenecek bir koleksiyon seçin',

View File

@@ -396,6 +396,7 @@ export const ukTranslations: DefaultTranslationsObject = {
height: 'Висота',
lessInfo: 'Менше інформації',
moreInfo: 'Більше інформації',
noFile: 'Немає файлу',
pasteURL: 'Вставити URL',
previewSizes: 'Попередній перегляд розмірів',
selectCollectionToBrowse: 'Оберіть колекцію для перегляду',

View File

@@ -396,6 +396,7 @@ export const viTranslations: DefaultTranslationsObject = {
height: 'Chiều cao',
lessInfo: 'Hiển thị ít hơn',
moreInfo: 'Thêm',
noFile: 'Không có tệp',
pasteURL: 'Dán URL',
previewSizes: 'Kích cỡ xem trước',
selectCollectionToBrowse: 'Chọn một Collection để tìm',

View File

@@ -384,6 +384,7 @@ export const zhTranslations: DefaultTranslationsObject = {
height: '高度',
lessInfo: '更少信息',
moreInfo: '更多信息',
noFile: '没有文件',
pasteURL: '粘贴网址',
previewSizes: '预览尺寸',
selectCollectionToBrowse: '选择一个要浏览的集合',

View File

@@ -384,6 +384,7 @@ export const zhTwTranslations: DefaultTranslationsObject = {
height: '高度',
lessInfo: '更少資訊',
moreInfo: '更多資訊',
noFile: '沒有檔案',
pasteURL: '貼上網址',
previewSizes: '預覽尺寸',
selectCollectionToBrowse: '選擇一個要瀏覽的集合',

View File

@@ -139,7 +139,7 @@ export function FileSidebar() {
))
: null}
{forms.map(({ errorCount, formState }, index) => {
const currentFile = formState.file.value as File
const currentFile = (formState?.file?.value as File) || ({} as File)
return (
<div
@@ -159,14 +159,16 @@ export function FileSidebar() {
>
<Thumbnail
className={`${baseClass}__thumbnail`}
fileSrc={isImage(currentFile.type) ? thumbnailUrls[index] : undefined}
fileSrc={isImage(currentFile.type) ? thumbnailUrls[index] : null}
/>
<div className={`${baseClass}__fileDetails`}>
<p className={`${baseClass}__fileName`} title={currentFile.name}>
{currentFile.name}
{currentFile.name || t('upload:noFile')}
</p>
</div>
<p className={`${baseClass}__fileSize`}>{getFileSize(currentFile)}</p>
{currentFile instanceof File ? (
<p className={`${baseClass}__fileSize`}>{getFileSize(currentFile)}</p>
) : null}
<div className={`${baseClass}__remove ${baseClass}__remove--underlay`}>
<XIcon />
</div>

View File

@@ -358,7 +358,7 @@ export function FormsManagerProvider({ children }: FormsManagerProps) {
}),
}
if (req.status === 413) {
if (req.status === 413 || req.status === 400) {
// file too large
currentForms[i] = {
...currentForms[i],

View File

@@ -43,7 +43,7 @@ export const Thumbnail: React.FC<ThumbnailProps> = (props) => {
}
}, [fileSrc, mimeType])
let src: string = ''
let src: null | string = null
/**
* If an imageCacheTag is provided, append it to the fileSrc

View File

@@ -974,7 +974,7 @@ describe('Uploads', () => {
const errorCount = page
.locator('#bulk-upload-drawer-slug-1 .file-selections .error-pill__count')
.first()
await expect(errorCount).toHaveText('2')
await expect(errorCount).toHaveText('3')
await page.locator('#bulk-upload-drawer-slug-1 .edit-many-bulk-uploads__toggle').click()
const editManyBulkUploadModal = page.locator('#edit-uploads-2-bulk-uploads')
@@ -997,6 +997,73 @@ describe('Uploads', () => {
await saveDocAndAssert(page)
})
test('should show validation error when bulk uploading files and then soft removing one of the files', async () => {
// Navigate to the upload creation page
await page.goto(uploadsOne.create)
// Upload single file
await page.setInputFiles(
'.file-field input[type="file"]',
path.resolve(dirname, './image.png'),
)
const filename = page.locator('.file-field__filename')
await expect(filename).toHaveValue('image.png')
const bulkUploadButton = page.locator('#field-hasManyUpload button', {
hasText: exactText('Create New'),
})
await bulkUploadButton.click()
const bulkUploadModal = page.locator('#bulk-upload-drawer-slug-1')
await expect(bulkUploadModal).toBeVisible()
// Bulk upload multiple files at once
await page.setInputFiles('#bulk-upload-drawer-slug-1 .dropzone input[type="file"]', [
path.resolve(dirname, './image.png'),
path.resolve(dirname, './test-image.png'),
])
await page.locator('#bulk-upload-drawer-slug-1 .edit-many-bulk-uploads__toggle').click()
const editManyBulkUploadModal = page.locator('#edit-uploads-2-bulk-uploads')
await expect(editManyBulkUploadModal).toBeVisible()
const fieldSelector = page.locator('.edit-many-bulk-uploads__form .react-select')
await fieldSelector.click({ delay: 100 })
const options = page.locator('.rs__option')
// Select an option
await options.locator('text=Prefix').click()
await page.locator('#edit-uploads-2-bulk-uploads #field-prefix').fill('some prefix')
await page.locator('.edit-many-bulk-uploads__sidebar-wrap button').click()
await page
.locator('#bulk-upload-drawer-slug-1 .file-field__upload .file-field__remove')
.click()
const chevronRight = page.locator(
'#bulk-upload-drawer-slug-1 .bulk-upload--actions-bar__controls button:nth-of-type(2)',
)
await chevronRight.click()
await expect(
page
.locator(
'#bulk-upload-drawer-slug-1 .file-selections .file-selections__fileRow .file-selections__fileName',
)
.first(),
).toContainText('No file')
const saveButton = page.locator('.bulk-upload--actions-bar__saveButtons button')
await saveButton.click()
const errorCount = page
.locator('#bulk-upload-drawer-slug-1 .file-selections .error-pill__count')
.first()
await expect(errorCount).toHaveText('1')
})
})
describe('remote url fetching', () => {

View File

@@ -54,6 +54,7 @@ export type SupportedTimezones =
| 'Asia/Singapore'
| 'Asia/Tokyo'
| 'Asia/Seoul'
| 'Australia/Brisbane'
| 'Australia/Sydney'
| 'Pacific/Guam'
| 'Pacific/Noumea'