From 4c9757cba2fe9da34a55a1e35e8d5001812e71b7 Mon Sep 17 00:00:00 2001 From: Caleb Pollman Date: Fri, 15 Nov 2024 10:02:36 -0800 Subject: [PATCH 1/6] feat(react-storage): skip adding too big files to upload queue --- .../UploadView/UploadView.tsx | 2 + .../LocationActionView/UploadView/types.ts | 7 +++ .../UploadView/useUploadView.ts | 45 +++++++++++++++++-- .../views/LocationActionView/constants.ts | 2 + 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.tsx b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.tsx index 807008ffa09..de128a78d0f 100644 --- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.tsx +++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.tsx @@ -52,6 +52,7 @@ export function UploadView({ location, tasks, statusCounts, + invalidFilesMessage, onActionStart, onActionCancel, onDropFiles, @@ -59,6 +60,7 @@ export function UploadView({ onTaskRemove, onSelectFiles, onToggleOverwrite, + onInvalidFilesMessageDismiss, } = useUploadView(props); const isActionStartDisabled = diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/types.ts b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/types.ts index 167c9669153..f1ae9912644 100644 --- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/types.ts +++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/types.ts @@ -6,11 +6,18 @@ import { ActionViewState, } from '../types'; +export interface UploadViewMessage { + id: string; + content: string; +} + export interface UploadViewState extends ActionViewState { isOverwritingEnabled: boolean; onDropFiles: (files: File[]) => void; onSelectFiles: (type: 'FILE' | 'FOLDER') => void; onToggleOverwrite: () => void; + invalidFilesMessage: UploadViewMessage | undefined; + onInvalidFilesMessageDismiss: (() => void) | undefined; } export interface UploadViewProps diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.ts b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.ts index 674fa9cb4a3..a75bdc29343 100644 --- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.ts +++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.ts @@ -3,21 +3,45 @@ import React from 'react'; import { uploadHandler } from '../../../actions'; import { useGetActionInput } from '../../../providers/configuration'; -import { useStore } from '../../../providers/store'; +import { FileItems, useStore } from '../../../providers/store'; import { Task, useProcessTasks } from '../../../tasks'; -import { DEFAULT_ACTION_CONCURRENCY } from '../constants'; +import { + DEFAULT_ACTION_CONCURRENCY, + UPLOAD_FILE_SIZE_LIMIT, + UPLOAD_FILE_SIZE_LIMIT_HUMAN, +} from '../constants'; import { UploadViewState, UseUploadViewOptions } from './types'; import { DEFAULT_OVERWRITE_ENABLED } from './constants'; +import { isUndefined } from '@aws-amplify/ui'; export const useUploadView = ( options?: UseUploadViewOptions ): UploadViewState => { + const [invalidFiles, setInvalidFiles] = React.useState< + FileItems | undefined + >(); const { onExit: _onExit } = options ?? {}; const getInput = useGetActionInput(); const [{ files, location }, dispatchStoreAction] = useStore(); const { current, key } = location; + const validFiles = React.useMemo( + () => + files?.filter((fileItem) => { + const { id, file } = fileItem; + if (file.size > UPLOAD_FILE_SIZE_LIMIT) { + setInvalidFiles((prev) => + isUndefined(prev) ? [fileItem] : prev.concat(fileItem) + ); + dispatchStoreAction({ type: 'REMOVE_FILE_ITEM', id }); + return false; + } + return true; + }), + [files, dispatchStoreAction] + ); + const [isOverwritingEnabled, setIsOverwritingEnabled] = React.useState( DEFAULT_OVERWRITE_ENABLED ); @@ -25,7 +49,7 @@ export const useUploadView = ( const [ { isProcessing, isProcessingComplete, statusCounts, tasks }, handleProcess, - ] = useProcessTasks(uploadHandler, files, { + ] = useProcessTasks(uploadHandler, validFiles, { concurrency: DEFAULT_ACTION_CONCURRENCY, }); @@ -76,16 +100,31 @@ export const useUploadView = ( [dispatchStoreAction] ); + const invalidFilesMessage = React.useMemo(() => { + if (invalidFiles?.length) { + const fileNames = invalidFiles.map(({ file }) => file.name).join(', '); + const content = `File ${fileNames} exceeds the size limit ${UPLOAD_FILE_SIZE_LIMIT_HUMAN}. Failed to add to upload queue.`; + return { id: crypto.randomUUID(), content }; + } + return undefined; + }, [invalidFiles]); + + const onInvalidFilesMessageDismiss = React.useCallback(() => { + setInvalidFiles(undefined); + }, []); + return { isProcessing, isProcessingComplete, isOverwritingEnabled, location, + invalidFilesMessage, statusCounts, tasks, onActionCancel, onActionExit, onActionStart, + onInvalidFilesMessageDismiss, onDropFiles, onTaskRemove, onSelectFiles, diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/constants.ts b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/constants.ts index 3ec4f59938a..f8f1d8722a7 100644 --- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/constants.ts +++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/constants.ts @@ -1 +1,3 @@ export const DEFAULT_ACTION_CONCURRENCY = 4; +export const UPLOAD_FILE_SIZE_LIMIT = 160 * 1000 * 1000 * 1000; +export const UPLOAD_FILE_SIZE_LIMIT_HUMAN = '160 GB'; From 1d041e35807ffa05dfb15a807dab447dedeecc35 Mon Sep 17 00:00:00 2001 From: Allan Zheng Date: Fri, 15 Nov 2024 14:36:40 -0800 Subject: [PATCH 2/6] chore: update display text --- .../displayText/libraries/en/uploadView.ts | 11 +++++++++++ .../components/StorageBrowser/displayText/types.ts | 3 +++ 2 files changed, 14 insertions(+) diff --git a/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts b/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts index 639e22003c3..482953d9df1 100644 --- a/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts +++ b/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts @@ -1,5 +1,6 @@ import { DEFAULT_ACTION_VIEW_DISPLAY_TEXT } from './shared'; import { DefaultUploadViewDisplayText } from '../../types'; +import { UPLOAD_FILE_SIZE_LIMIT_HUMAN } from '../../../views/LocationActionView/constants'; export const DEFAULT_UPLOAD_VIEW_DISPLAY_TEXT: DefaultUploadViewDisplayText = { ...DEFAULT_ACTION_VIEW_DISPLAY_TEXT, @@ -79,6 +80,16 @@ export const DEFAULT_UPLOAD_VIEW_DISPLAY_TEXT: DefaultUploadViewDisplayText = { return { content: 'All files uploaded.', type }; }, + getInvalidFilesMessage: (data) => { + const fileNames = data?.files?.map(({ name }) => name).join(', ') ?? ''; + if (!fileNames) { + return undefined; + } + return { + content: `File ${fileNames} exceeds the size limit 160 GB. Failed to add to upload queue.`, + type: 'warning', + }; + }, statusDisplayOverwritePreventedLabel: 'Overwrite prevented', overwriteToggleLabel: 'Overwrite existing files', }; diff --git a/packages/react-storage/src/components/StorageBrowser/displayText/types.ts b/packages/react-storage/src/components/StorageBrowser/displayText/types.ts index 9522acb0536..5d9a187bd2d 100644 --- a/packages/react-storage/src/components/StorageBrowser/displayText/types.ts +++ b/packages/react-storage/src/components/StorageBrowser/displayText/types.ts @@ -83,6 +83,9 @@ export interface DefaultActionViewDisplayText { counts?: StatusCounts; tasks?: Tasks; }) => { content?: string; type?: MessageType } | undefined; + getInvalidFilesMessage: (data?: { + files?: { name: string }[]; + }) => { content?: string; type?: MessageType } | undefined; statusDisplayCanceledLabel: string; statusDisplayCompletedLabel: string; statusDisplayFailedLabel: string; From 703ba050127aa6f668022c91894be843f09b4019 Mon Sep 17 00:00:00 2001 From: Allan Zheng Date: Fri, 15 Nov 2024 17:22:26 -0800 Subject: [PATCH 3/6] fix: update the invalid files state to simplify interfacs --- .../__snapshots__/uploadView.spec.ts.snap | 1 + .../displayText/libraries/en/shared.ts | 5 ++- .../displayText/libraries/en/uploadView.ts | 11 +++--- .../StorageBrowser/displayText/types.ts | 9 ++--- .../UploadView/UploadView.tsx | 6 ++-- .../UploadView/__tests__/UploadView.spec.tsx | 12 ++++++- .../__tests__/useUploadView.spec.ts | 35 +++++++++++++++---- .../LocationActionView/UploadView/types.ts | 10 ++---- .../UploadView/useUploadView.ts | 22 ++++-------- .../views/LocationActionView/constants.ts | 1 - 10 files changed, 70 insertions(+), 42 deletions(-) diff --git a/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/__tests__/__snapshots__/uploadView.spec.ts.snap b/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/__tests__/__snapshots__/uploadView.spec.ts.snap index 3f5b9c37eb3..002c1beefb9 100644 --- a/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/__tests__/__snapshots__/uploadView.spec.ts.snap +++ b/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/__tests__/__snapshots__/uploadView.spec.ts.snap @@ -58,6 +58,7 @@ exports[`CopyView display text values should match snapshot values 1`] = ` "addFilesLabel": "Add files", "addFolderLabel": "Add folder", "getActionCompleteMessage": [Function], + "getFilesValidationMessage": [Function], "overwriteToggleLabel": "Overwrite existing files", "statusDisplayCanceledLabel": "Canceled", "statusDisplayCompletedLabel": "Completed", diff --git a/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/shared.ts b/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/shared.ts index 6a66de6b758..3822abe841e 100644 --- a/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/shared.ts +++ b/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/shared.ts @@ -5,7 +5,10 @@ import { export const DEFAULT_ACTION_VIEW_DISPLAY_TEXT: Omit< DefaultActionViewDisplayText, - 'actionStartLabel' | 'getActionCompleteMessage' | 'title' + | 'actionStartLabel' + | 'getActionCompleteMessage' + | 'title' + | 'getFilesValidationMessage' > = { actionCancelLabel: 'Cancel', actionExitLabel: 'Exit', diff --git a/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts b/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts index 482953d9df1..5c98f4ca0e7 100644 --- a/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts +++ b/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts @@ -1,6 +1,6 @@ import { DEFAULT_ACTION_VIEW_DISPLAY_TEXT } from './shared'; import { DefaultUploadViewDisplayText } from '../../types'; -import { UPLOAD_FILE_SIZE_LIMIT_HUMAN } from '../../../views/LocationActionView/constants'; +import { UPLOAD_FILE_SIZE_LIMIT } from '../../../views/LocationActionView/constants'; export const DEFAULT_UPLOAD_VIEW_DISPLAY_TEXT: DefaultUploadViewDisplayText = { ...DEFAULT_ACTION_VIEW_DISPLAY_TEXT, @@ -80,11 +80,14 @@ export const DEFAULT_UPLOAD_VIEW_DISPLAY_TEXT: DefaultUploadViewDisplayText = { return { content: 'All files uploaded.', type }; }, - getInvalidFilesMessage: (data) => { - const fileNames = data?.files?.map(({ name }) => name).join(', ') ?? ''; - if (!fileNames) { + getFilesValidationMessage: ({ invalidFiles } = {}) => { + if (!invalidFiles) { return undefined; } + const fileNames = invalidFiles + .filter(({ file }) => file.size > UPLOAD_FILE_SIZE_LIMIT) + .map(({ file }) => file.name) + .join(', '); return { content: `File ${fileNames} exceeds the size limit 160 GB. Failed to add to upload queue.`, type: 'warning', diff --git a/packages/react-storage/src/components/StorageBrowser/displayText/types.ts b/packages/react-storage/src/components/StorageBrowser/displayText/types.ts index 5d9a187bd2d..dfcead979a0 100644 --- a/packages/react-storage/src/components/StorageBrowser/displayText/types.ts +++ b/packages/react-storage/src/components/StorageBrowser/displayText/types.ts @@ -1,6 +1,7 @@ import { StatusCounts, Tasks } from '../tasks'; import { CopyHandlerData, + CreateFolderHandlerData, DeleteHandlerData, FolderData, LocationData, @@ -11,7 +12,7 @@ import { } from '../actions'; import { LocationState } from '../providers/store/location'; import { MessageType } from '../composables/Message'; -import { CreateFolderHandlerData } from '../actions'; +import { FileItems } from '../providers'; /** * Common list view display text values @@ -83,9 +84,6 @@ export interface DefaultActionViewDisplayText { counts?: StatusCounts; tasks?: Tasks; }) => { content?: string; type?: MessageType } | undefined; - getInvalidFilesMessage: (data?: { - files?: { name: string }[]; - }) => { content?: string; type?: MessageType } | undefined; statusDisplayCanceledLabel: string; statusDisplayCompletedLabel: string; statusDisplayFailedLabel: string; @@ -139,6 +137,9 @@ export interface DefaultUploadViewDisplayText addFolderLabel: string; statusDisplayOverwritePreventedLabel: string; overwriteToggleLabel: string; + getFilesValidationMessage: (data?: { + invalidFiles?: FileItems; + }) => { content?: string; type?: MessageType } | undefined; } export interface DefaultStorageBrowserDisplayText { diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.tsx b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.tsx index de128a78d0f..03ccea99c5f 100644 --- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.tsx +++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.tsx @@ -43,6 +43,7 @@ export function UploadView({ overwriteToggleLabel, title, getActionCompleteMessage, + getFilesValidationMessage, } = displayText; const { @@ -52,7 +53,7 @@ export function UploadView({ location, tasks, statusCounts, - invalidFilesMessage, + invalidFiles, onActionStart, onActionCancel, onDropFiles, @@ -60,7 +61,6 @@ export function UploadView({ onTaskRemove, onSelectFiles, onToggleOverwrite, - onInvalidFilesMessageDismiss, } = useUploadView(props); const isActionStartDisabled = @@ -75,6 +75,8 @@ export function UploadView({ ? getActionCompleteMessage({ counts: statusCounts, }) + : invalidFiles && !isProcessing + ? getFilesValidationMessage({ invalidFiles }) : undefined; return ( diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/__tests__/UploadView.spec.tsx b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/__tests__/UploadView.spec.tsx index 099a8ed67da..f6fdefe17b7 100644 --- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/__tests__/UploadView.spec.tsx +++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/__tests__/UploadView.spec.tsx @@ -10,7 +10,10 @@ import { UploadView } from '../UploadView'; jest.mock('../../../../displayText', () => ({ useDisplayText: () => ({ - UploadView: { getActionCompleteMessage: jest.fn() }, + UploadView: { + getActionCompleteMessage: jest.fn(), + getFilesValidationMessage: jest.fn(), + }, }), })); @@ -42,6 +45,11 @@ const statusCounts = { ...INITIAL_STATUS_COUNTS }; const testFile = new File([], 'test-ooo'); const data = { id: 'some-uuid', file: testFile, key: testFile.name }; +const invalidFileData = { + file: new File([], 'very-big-file'), + id: 'uuid', + key: 'very-big-file', +}; const taskOne = { data, @@ -67,6 +75,7 @@ const initialViewState: UploadViewState = { isProcessingComplete: false, isProcessing: false, tasks: [], + invalidFiles: undefined, statusCounts, }; @@ -74,6 +83,7 @@ const preprocessingViewState: UploadViewState = { ...initialViewState, tasks: [taskOne], statusCounts: { ...statusCounts, QUEUED: 1, TOTAL: 1 }, + invalidFiles: [invalidFileData], }; const processingViewState: UploadViewState = { diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/__tests__/useUploadView.spec.ts b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/__tests__/useUploadView.spec.ts index 63883f432a0..b4a19c6ea32 100644 --- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/__tests__/useUploadView.spec.ts +++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/__tests__/useUploadView.spec.ts @@ -4,6 +4,7 @@ import { LocationData } from '../../../../actions'; import * as ConfigModule from '../../../../providers/configuration'; import * as StoreModule from '../../../../providers/store'; import * as TasksModule from '../../../../tasks'; +import { UPLOAD_FILE_SIZE_LIMIT } from '../../constants'; const useStoreSpy = jest.spyOn(StoreModule, 'useStore'); @@ -16,13 +17,12 @@ const rootLocation: LocationData = { type: 'BUCKET', }; +const mockUserStoreState = { + location: { current: rootLocation, path: '', key: '' }, + files: undefined, +} as StoreModule.UseStoreState; const dispatchStoreAction = jest.fn(); -useStoreSpy.mockReturnValue([ - { - location: { current: rootLocation, path: '', key: '' }, - } as StoreModule.UseStoreState, - dispatchStoreAction, -]); +useStoreSpy.mockReturnValue([mockUserStoreState, dispatchStoreAction]); const credentials = jest.fn(); const config: ConfigModule.GetActionInput = jest.fn(() => ({ @@ -43,6 +43,15 @@ const fileItemTwo = { file: testFileTwo, key: testFileTwo.name, }; +const invalidFile = { + ...new File([], 'invalid-file'), + size: UPLOAD_FILE_SIZE_LIMIT + 1, +}; +const invalidFileItem = { + id: 'invalid-file-uuid', + file: invalidFile, + key: invalidFile.name, +}; jest.spyOn(ConfigModule, 'useGetActionInput').mockReturnValue(config); const handleProcessTasks = jest.fn(); @@ -77,6 +86,7 @@ const useProcessTasksSpy = jest describe('useUploadView', () => { afterEach(() => { + mockUserStoreState.files = undefined; jest.clearAllMocks(); }); @@ -94,6 +104,18 @@ describe('useUploadView', () => { }); }); + it('should dispatchStoreAction when onRemoveFile is invoked', () => { + mockUserStoreState.files = [invalidFileItem]; + const { result } = renderHook(() => useUploadView()); + + expect(dispatchStoreAction).toHaveBeenCalledWith({ + type: 'REMOVE_FILE_ITEM', + id: invalidFileItem.id, + }); + + expect(result.current.invalidFiles).toEqual([invalidFileItem]); + }); + it('should dispatchStoreAction when onSelectFiles is invoked with different types', () => { const { result } = renderHook(() => useUploadView()); @@ -134,6 +156,7 @@ describe('useUploadView', () => { destinationPrefix: '', }); }); + it('should call cancel on each pending task when onCancel is invoked', () => { const tasks: TasksModule.Task[] = [ { ...taskOne, status: 'PENDING' }, diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/types.ts b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/types.ts index f1ae9912644..afa539fb1a9 100644 --- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/types.ts +++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/types.ts @@ -1,23 +1,19 @@ import { LocationData, UploadHandlerData } from '../../../actions'; -import { FileItem } from '../../../providers'; +import { FileItem, FileItems } from '../../../providers'; import { ActionViewComponent, ActionViewProps, ActionViewState, } from '../types'; -export interface UploadViewMessage { - id: string; - content: string; -} +export type InvalidFileReason = 'FILE_TOO_BIG'; export interface UploadViewState extends ActionViewState { isOverwritingEnabled: boolean; onDropFiles: (files: File[]) => void; onSelectFiles: (type: 'FILE' | 'FOLDER') => void; onToggleOverwrite: () => void; - invalidFilesMessage: UploadViewMessage | undefined; - onInvalidFilesMessageDismiss: (() => void) | undefined; + invalidFiles: FileItems | undefined; } export interface UploadViewProps diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.ts b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.ts index a75bdc29343..16601b78b31 100644 --- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.ts +++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.ts @@ -9,7 +9,6 @@ import { Task, useProcessTasks } from '../../../tasks'; import { DEFAULT_ACTION_CONCURRENCY, UPLOAD_FILE_SIZE_LIMIT, - UPLOAD_FILE_SIZE_LIMIT_HUMAN, } from '../constants'; import { UploadViewState, UseUploadViewOptions } from './types'; import { DEFAULT_OVERWRITE_ENABLED } from './constants'; @@ -51,6 +50,11 @@ export const useUploadView = ( handleProcess, ] = useProcessTasks(uploadHandler, validFiles, { concurrency: DEFAULT_ACTION_CONCURRENCY, + onTaskProgress: () => { + if (invalidFiles) { + setInvalidFiles(undefined); + } + }, }); const onDropFiles = React.useCallback( @@ -100,31 +104,17 @@ export const useUploadView = ( [dispatchStoreAction] ); - const invalidFilesMessage = React.useMemo(() => { - if (invalidFiles?.length) { - const fileNames = invalidFiles.map(({ file }) => file.name).join(', '); - const content = `File ${fileNames} exceeds the size limit ${UPLOAD_FILE_SIZE_LIMIT_HUMAN}. Failed to add to upload queue.`; - return { id: crypto.randomUUID(), content }; - } - return undefined; - }, [invalidFiles]); - - const onInvalidFilesMessageDismiss = React.useCallback(() => { - setInvalidFiles(undefined); - }, []); - return { isProcessing, isProcessingComplete, isOverwritingEnabled, location, - invalidFilesMessage, + invalidFiles, statusCounts, tasks, onActionCancel, onActionExit, onActionStart, - onInvalidFilesMessageDismiss, onDropFiles, onTaskRemove, onSelectFiles, diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/constants.ts b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/constants.ts index f8f1d8722a7..a7eb48005dd 100644 --- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/constants.ts +++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/constants.ts @@ -1,3 +1,2 @@ export const DEFAULT_ACTION_CONCURRENCY = 4; export const UPLOAD_FILE_SIZE_LIMIT = 160 * 1000 * 1000 * 1000; -export const UPLOAD_FILE_SIZE_LIMIT_HUMAN = '160 GB'; From 69ca33d390a2bf5563b3cab5041875623ebdea14 Mon Sep 17 00:00:00 2001 From: Allan Zheng Date: Fri, 15 Nov 2024 17:42:42 -0800 Subject: [PATCH 4/6] chore: fix display text --- .../StorageBrowser/displayText/libraries/en/shared.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/shared.ts b/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/shared.ts index 3822abe841e..6a66de6b758 100644 --- a/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/shared.ts +++ b/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/shared.ts @@ -5,10 +5,7 @@ import { export const DEFAULT_ACTION_VIEW_DISPLAY_TEXT: Omit< DefaultActionViewDisplayText, - | 'actionStartLabel' - | 'getActionCompleteMessage' - | 'title' - | 'getFilesValidationMessage' + 'actionStartLabel' | 'getActionCompleteMessage' | 'title' > = { actionCancelLabel: 'Cancel', actionExitLabel: 'Exit', From cc4056f5a639cebbe46c1c5654e1714129a005c8 Mon Sep 17 00:00:00 2001 From: Allan Zheng Date: Fri, 15 Nov 2024 19:32:47 -0800 Subject: [PATCH 5/6] chore: address feedbacks --- .../StorageBrowser/displayText/libraries/en/uploadView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts b/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts index 5c98f4ca0e7..c3025259be3 100644 --- a/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts +++ b/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts @@ -89,7 +89,7 @@ export const DEFAULT_UPLOAD_VIEW_DISPLAY_TEXT: DefaultUploadViewDisplayText = { .map(({ file }) => file.name) .join(', '); return { - content: `File ${fileNames} exceeds the size limit 160 GB. Failed to add to upload queue.`, + content: `These files cannot be added to the upload queue due to they are larger than 160GB respectively: ${fileNames}`, type: 'warning', }; }, From 29af691f479239731142da38f84016318d80ca17 Mon Sep 17 00:00:00 2001 From: Allan Zheng Date: Sat, 16 Nov 2024 00:27:50 -0800 Subject: [PATCH 6/6] fix: attempt to add invalid files to filesProvider --- .../displayText/libraries/en/uploadView.ts | 2 +- .../store/files/__tests__/context.spec.ts | 19 ++- .../store/files/__tests__/utils.spec.ts | 140 +++++++++++++----- .../providers/store/files/context.tsx | 5 +- .../providers/store/files/types.ts | 10 +- .../providers/store/files/utils.ts | 94 ++++++++---- .../providers/store/useStore.ts | 7 +- .../UploadView/UploadView.tsx | 11 +- .../__tests__/useUploadView.spec.ts | 31 +++- .../LocationActionView/UploadView/types.ts | 2 - .../UploadView/useUploadView.ts | 31 +--- 11 files changed, 238 insertions(+), 114 deletions(-) diff --git a/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts b/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts index c3025259be3..cba6ae48564 100644 --- a/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts +++ b/packages/react-storage/src/components/StorageBrowser/displayText/libraries/en/uploadView.ts @@ -81,7 +81,7 @@ export const DEFAULT_UPLOAD_VIEW_DISPLAY_TEXT: DefaultUploadViewDisplayText = { return { content: 'All files uploaded.', type }; }, getFilesValidationMessage: ({ invalidFiles } = {}) => { - if (!invalidFiles) { + if (!invalidFiles?.length) { return undefined; } const fileNames = invalidFiles diff --git a/packages/react-storage/src/components/StorageBrowser/providers/store/files/__tests__/context.spec.ts b/packages/react-storage/src/components/StorageBrowser/providers/store/files/__tests__/context.spec.ts index d85ec8ce08e..7689355318d 100644 --- a/packages/react-storage/src/components/StorageBrowser/providers/store/files/__tests__/context.spec.ts +++ b/packages/react-storage/src/components/StorageBrowser/providers/store/files/__tests__/context.spec.ts @@ -2,6 +2,7 @@ import { act, renderHook } from '@testing-library/react'; import { FilesProvider, useFiles } from '../context'; import * as UIReactModule from '@aws-amplify/ui-react/internal'; +import { UPLOAD_FILE_SIZE_LIMIT } from '../../../../views/LocationActionView/constants'; let uuid = 0; Object.defineProperty(globalThis, 'crypto', { @@ -20,7 +21,7 @@ describe('useFiles', () => { const { result } = renderHook(() => useFiles(), { wrapper: FilesProvider }); const [state, handler] = result.current; - expect(state).toStrictEqual([]); + expect(state).toStrictEqual({ validFiles: [], invalidFiles: [] }); expect(typeof handler).toBe('function'); }); @@ -39,22 +40,30 @@ describe('useFiles', () => { expect(handleFileSelect).toHaveBeenCalledTimes(1); }); - it('adds files as as expected', () => { + it('adds files as expected', () => { const fileOne = new File([], 'file-one'); const fileTwo = new File([], 'file-two'); + const invalidFileThree = { + ...new File([], 'file-three-invalid'), + size: UPLOAD_FILE_SIZE_LIMIT + 1, + }; const { result } = renderHook(() => useFiles(), { wrapper: FilesProvider }); const [initState, handler] = result.current; - expect(initState).toStrictEqual([]); + expect(initState).toStrictEqual({ validFiles: [], invalidFiles: [] }); act(() => { - handler({ type: 'ADD_FILE_ITEMS', files: [fileOne, fileTwo] }); + handler({ + type: 'ADD_FILE_ITEMS', + files: [fileOne, fileTwo, invalidFileThree], + }); }); const [nextState] = result.current; - expect(nextState).toHaveLength(2); + expect(nextState?.validFiles).toHaveLength(2); + expect(nextState?.invalidFiles).toHaveLength(1); }); }); diff --git a/packages/react-storage/src/components/StorageBrowser/providers/store/files/__tests__/utils.spec.ts b/packages/react-storage/src/components/StorageBrowser/providers/store/files/__tests__/utils.spec.ts index 3d205e0eacc..d7e0266e0e8 100644 --- a/packages/react-storage/src/components/StorageBrowser/providers/store/files/__tests__/utils.spec.ts +++ b/packages/react-storage/src/components/StorageBrowser/providers/store/files/__tests__/utils.spec.ts @@ -1,4 +1,5 @@ -import { FileItems, FileItem } from '../types'; +import { UPLOAD_FILE_SIZE_LIMIT } from '../../../../views/LocationActionView/constants'; +import { FileItem } from '../types'; import { resolveFiles, filesReducer, parseFileSelectParams } from '../utils'; let uuid = 0; @@ -14,6 +15,10 @@ Object.defineProperty(globalThis, 'crypto', { const fileOne = new File([], 'file-one'); const fileTwo = new File([], 'file-two'); const fileThree = new File([], 'file-three'); +const invalidFile = { + ...new File([], 'file-invalid'), + size: UPLOAD_FILE_SIZE_LIMIT + 1, +}; const fileItemOne: FileItem = { id: 'item-one', @@ -27,17 +32,29 @@ const fileItemTwo: FileItem = { key: fileTwo.name, }; +const invalidFileItem: FileItem = { + id: 'item-invalid', + file: invalidFile, + key: invalidFile.name, +}; + describe('files context utils', () => { describe('resolveFiles', () => { it('returns the previous `items` when incoming `files` are `undefined`', () => { - const previous = [fileItemOne, fileItemTwo]; + const previous = { + validFiles: [fileItemOne, fileItemTwo], + invalidFiles: [], + }; const output = resolveFiles(previous, undefined); expect(output).toBe(previous); }); it('returns the previous `items` when incoming `files` is an empty array', () => { - const previous = [fileItemOne, fileItemTwo]; + const previous = { + validFiles: [fileItemOne, fileItemTwo], + invalidFiles: [], + }; const output = resolveFiles(previous, []); expect(output).toBe(previous); @@ -45,21 +62,26 @@ describe('files context utils', () => { it('returns the previous `items` when incoming `files` are all duplicates', () => { const incoming = [fileOne, fileTwo]; - const previous = [fileItemOne, fileItemTwo]; + const previous = { + validFiles: [fileItemOne, fileItemTwo], + invalidFiles: [], + }; const output = resolveFiles(previous, incoming); - - expect(output).toBe(previous); + expect(output).toEqual(previous); }); it('filters incoming `files` that exist in previous `items`', () => { const incoming = [fileOne, fileTwo, fileThree]; - const previous = [fileItemOne, fileItemTwo]; + const previous = { + validFiles: [fileItemOne, fileItemTwo], + invalidFiles: [], + }; const output = resolveFiles(previous, incoming); expect(output).not.toBe(previous); - expect(output).toHaveLength(3); + expect(output.validFiles).toHaveLength(3); - const newItem = output[1]; + const newItem = output.validFiles[1]; expect(newItem.file).toBe(fileThree); expect(newItem.key).toBe(fileThree.name); @@ -68,11 +90,11 @@ describe('files context utils', () => { it('returns the sorted next `items` when previous `items` are `undefined`', () => { const incoming = [fileTwo, fileOne]; - const previous: FileItems = []; + const previous = { validFiles: [], invalidFiles: [] }; const output = resolveFiles(previous, incoming); - expect(output).toHaveLength(2); - const [itemOne, itemTwo] = output; + expect(output.validFiles).toHaveLength(2); + const [itemOne, itemTwo] = output.validFiles; expect(itemOne.file).toBe(fileOne); expect(itemTwo.file).toBe(fileTwo); @@ -80,12 +102,15 @@ describe('files context utils', () => { it('merges, sorts and returns previous and next `items`', () => { const incoming = [fileThree]; - const previous = [fileItemOne, fileItemTwo]; + const previous = { + validFiles: [fileItemOne, fileItemTwo], + invalidFiles: [], + }; const output = resolveFiles(previous, incoming); - expect(output).toHaveLength(3); + expect(output.validFiles).toHaveLength(3); - const [itemOne, itemTwo, itemThree] = output; + const [itemOne, itemTwo, itemThree] = output.validFiles; // fileItemOne.key === 'item-one' expect(itemOne.key).toBe(fileItemOne.key); @@ -98,61 +123,108 @@ describe('files context utils', () => { }); it('returns the webKitRelativePath as key when available', () => { - const incoming = [ - { ...fileThree, webkitRelativePath: 'test/file/file-three' }, - ]; - const previous = [fileItemOne, fileItemTwo]; - const output = resolveFiles(previous, incoming); + const newFile = new File([], 'new-file'); + Object.assign(newFile, { webkitRelativePath: 'test/file/new-file' }); + const incoming = [newFile]; + const previous = { + validFiles: [fileItemOne, fileItemTwo], + invalidFiles: [], + }; + const { validFiles: output } = resolveFiles(previous, incoming); expect(output).toHaveLength(3); - expect(output[2].key).toBe('test/file/file-three'); + expect(output[2].key).toBe('test/file/new-file'); }); }); describe('filesReducer', () => { it('adds `fileItems` as expected', () => { const incoming = [fileOne, fileTwo, fileThree]; - const previous = [fileItemOne, fileItemTwo]; + const previous = { + validFiles: [fileItemOne, fileItemTwo], + invalidFiles: [], + }; const output = filesReducer(previous, { type: 'ADD_FILE_ITEMS', files: incoming, }); - expect(output).toHaveLength(3); + expect(output.validFiles).toHaveLength(3); + expect(output.invalidFiles).toHaveLength(0); + }); + + it('adds `fileItems` that is invalid', () => { + const previous = { + validFiles: [fileItemOne, fileItemTwo], + invalidFiles: [], + }; + const output = filesReducer(previous, { + type: 'ADD_FILE_ITEMS', + files: [invalidFile], + }); + + expect(output.validFiles).toHaveLength(2); + expect(output.invalidFiles).toHaveLength(1); }); it('removes a `fileItem` as expected', () => { - const previous = [fileItemOne, fileItemTwo]; + const previous = { + validFiles: [fileItemOne, fileItemTwo], + invalidFiles: [], + }; const targetId = fileItemOne.id; - const output = filesReducer(previous, { + const { validFiles } = filesReducer(previous, { type: 'REMOVE_FILE_ITEM', id: targetId, }); - expect(output).toHaveLength(1); - expect(output[0]).toBe(fileItemTwo); + expect(validFiles).toHaveLength(1); + expect(validFiles[0]).toBe(fileItemTwo); }); it('returns the previous items on remove when previous and next items are the same length', () => { - const previous = [fileItemOne, fileItemTwo]; + const previous = { + validFiles: [fileItemOne, fileItemTwo], + invalidFiles: [], + }; const targetId = 'not a real id lol'; - const output = filesReducer(previous, { + const { validFiles: outputValidFiles } = filesReducer(previous, { type: 'REMOVE_FILE_ITEM', id: targetId, }); - expect(output).toHaveLength(2); - expect(output).toBe(previous); + expect(outputValidFiles).toHaveLength(2); + expect(outputValidFiles).toBe(previous.validFiles); }); it('resets `fileItems` as expected', () => { - const previous = [fileItemOne, fileItemTwo]; + const previous = { + validFiles: [fileItemOne, fileItemTwo], + invalidFiles: [invalidFileItem], + }; - const output = filesReducer(previous, { type: 'RESET_FILE_ITEMS' }); + const { validFiles, invalidFiles } = filesReducer(previous, { + type: 'RESET_FILE_ITEMS', + }); + + expect(validFiles).toHaveLength(0); + expect(invalidFiles).toHaveLength(0); + }); + + it('resets invalid `fileTimes` as expected', () => { + const previous = { + validFiles: [fileItemOne, fileItemTwo], + invalidFiles: [invalidFileItem], + }; + + const { validFiles, invalidFiles } = filesReducer(previous, { + type: 'RESET_INVALID_FILE_ITEMS', + }); - expect(output).toHaveLength(0); + expect(validFiles).toBe(previous.validFiles); + expect(invalidFiles).toHaveLength(0); }); }); diff --git a/packages/react-storage/src/components/StorageBrowser/providers/store/files/context.tsx b/packages/react-storage/src/components/StorageBrowser/providers/store/files/context.tsx index 64c93209520..141b7ddcea6 100644 --- a/packages/react-storage/src/components/StorageBrowser/providers/store/files/context.tsx +++ b/packages/react-storage/src/components/StorageBrowser/providers/store/files/context.tsx @@ -20,7 +20,10 @@ export const { FilesContext, useFiles } = createContextUtilities({ export function FilesProvider({ children, }: FilesProviderProps): React.JSX.Element { - const [items, dispatch] = React.useReducer(filesReducer, []); + const [items, dispatch] = React.useReducer(filesReducer, { + validFiles: [], + invalidFiles: [], + }); const [fileInput, handleFileSelect] = useFileSelect((nextFiles) => { dispatch({ type: 'ADD_FILE_ITEMS', files: nextFiles }); diff --git a/packages/react-storage/src/components/StorageBrowser/providers/store/files/types.ts b/packages/react-storage/src/components/StorageBrowser/providers/store/files/types.ts index b4076f19274..bf21a765fd2 100644 --- a/packages/react-storage/src/components/StorageBrowser/providers/store/files/types.ts +++ b/packages/react-storage/src/components/StorageBrowser/providers/store/files/types.ts @@ -4,7 +4,8 @@ export type FilesActionType = | { type: 'ADD_FILE_ITEMS'; files?: File[] } | { type: 'REMOVE_FILE_ITEM'; id: string } | { type: 'SELECT_FILES'; selectionType?: SelectionType } - | { type: 'RESET_FILE_ITEMS' }; + | { type: 'RESET_FILE_ITEMS' } + | { type: 'RESET_INVALID_FILE_ITEMS' }; export type HandleFilesAction = (input: FilesActionType) => void; @@ -14,7 +15,12 @@ export interface FileItem extends TaskData { export type FileItems = FileItem[]; -export type FilesContextType = [FileItems | undefined, HandleFilesAction]; +export interface FileItemsState { + validFiles: FileItems; + invalidFiles: FileItems; +} + +export type FilesContextType = [FileItemsState | undefined, HandleFilesAction]; export interface FilesProviderProps { children?: React.ReactNode; diff --git a/packages/react-storage/src/components/StorageBrowser/providers/store/files/utils.ts b/packages/react-storage/src/components/StorageBrowser/providers/store/files/utils.ts index f093f5f7c21..d662634bff9 100644 --- a/packages/react-storage/src/components/StorageBrowser/providers/store/files/utils.ts +++ b/packages/react-storage/src/components/StorageBrowser/providers/store/files/utils.ts @@ -4,46 +4,75 @@ import { isEmpty, isString, isUndefined } from '@aws-amplify/ui'; import { HandleFileSelect } from '@aws-amplify/ui-react/internal'; import { SelectionType } from '../../../actions/configs'; +// FIXME move to closer constant file. +import { UPLOAD_FILE_SIZE_LIMIT } from '../../../views/LocationActionView/constants'; -import { FileItem, FileItems, FilesActionType } from './types'; +import { FileItem, FileItemsState, FilesActionType } from './types'; const compareFileItems = (prev: FileItem, next: FileItem) => prev.key.localeCompare(next.key); +const isValidFile = (file: File) => file.size <= UPLOAD_FILE_SIZE_LIMIT; + +const isSameFiles = (prev: File, next: File) => + prev.name === next.name && + prev.webkitRelativePath === next.webkitRelativePath; + +const generateFileItem = (file: File): FileItem => ({ + key: isEmpty(file.webkitRelativePath) ? file.name : file.webkitRelativePath, + id: crypto.randomUUID(), + file, +}); + export const resolveFiles = ( - prevItems: FileItems, + prevItems: FileItemsState, files: File[] | undefined -): FileItems => { +): FileItemsState => { if (!files?.length) return prevItems; - // construct `nextItems` and filter out existing `file` entries - const nextItems = files.reduce((items: FileItems, file) => { - const { name, webkitRelativePath } = file; + const { validFiles: prevValidFiles, invalidFiles: prevInvalidFiles } = + prevItems; + + const nextValidFiles = files + .filter(isValidFile) + .filter( + (file) => + !prevValidFiles.some(({ file: existing }) => + isSameFiles(existing, file) + ) + ) + .map(generateFileItem); - return prevItems.some( - ({ file: existing }) => - existing.name === name && - existing.webkitRelativePath === webkitRelativePath + const nextInvalidFiles = files + .filter((file) => !isValidFile(file)) + .filter( + (file) => + !prevInvalidFiles.some(({ file: existing }) => + isSameFiles(existing, file) + ) ) - ? items - : items.concat({ - key: isEmpty(webkitRelativePath) ? name : webkitRelativePath, - id: crypto.randomUUID(), - file, - }); - }, []); - - if (!nextItems.length) return prevItems; - - if (!prevItems.length) { - return nextItems.sort(compareFileItems); + .map(generateFileItem); + + if (!prevValidFiles.length) { + nextValidFiles.sort(compareFileItems); + } + + if (!prevInvalidFiles.length) { + nextInvalidFiles.sort(compareFileItems); } - return prevItems.concat(nextItems).sort(compareFileItems); + return { + validFiles: nextValidFiles.length + ? prevValidFiles.concat(nextValidFiles).sort(compareFileItems) + : prevValidFiles, + invalidFiles: nextInvalidFiles.length + ? prevInvalidFiles.concat(nextInvalidFiles).sort(compareFileItems) + : prevInvalidFiles, + }; }; export const filesReducer: React.Reducer< - FileItems, + FileItemsState, Exclude > = (prevItems, input) => { switch (input.type) { @@ -51,16 +80,23 @@ export const filesReducer: React.Reducer< return resolveFiles(prevItems, input.files); } case 'REMOVE_FILE_ITEM': { - const filteredItems = prevItems.filter(({ id }) => id !== input.id); + const filteredValidFiles = prevItems.validFiles.filter( + ({ id }) => id !== input.id + ); - return filteredItems.length === prevItems.length + return filteredValidFiles.length === prevItems.validFiles.length ? prevItems - : filteredItems; + : { + validFiles: filteredValidFiles, + invalidFiles: prevItems.invalidFiles, + }; } case 'RESET_FILE_ITEMS': { - return []; + return { validFiles: [], invalidFiles: [] }; + } + case 'RESET_INVALID_FILE_ITEMS': { + return { ...prevItems, invalidFiles: [] }; } - // TODO: clear message } }; diff --git a/packages/react-storage/src/components/StorageBrowser/providers/store/useStore.ts b/packages/react-storage/src/components/StorageBrowser/providers/store/useStore.ts index 7ca935c82a9..b151d9203e9 100644 --- a/packages/react-storage/src/components/StorageBrowser/providers/store/useStore.ts +++ b/packages/react-storage/src/components/StorageBrowser/providers/store/useStore.ts @@ -1,7 +1,7 @@ import React from 'react'; import { ActionTypeAction, useActionType } from './actionType'; -import { FileItems, FilesActionType, useFiles } from './files'; +import { FileItemsState, FilesActionType, useFiles } from './files'; import { LocationActionType, LocationState, useLocation } from './location'; import { LocationItemsAction, @@ -11,7 +11,7 @@ import { export interface UseStoreState { actionType: string | undefined; - files: FileItems | undefined; + files: FileItemsState | undefined; location: LocationState; locationItems: LocationItemsState; } @@ -36,7 +36,8 @@ export function useStore(): [UseStoreState, HandleStoreAction] { case 'ADD_FILE_ITEMS': case 'REMOVE_FILE_ITEM': case 'SELECT_FILES': - case 'RESET_FILE_ITEMS': { + case 'RESET_FILE_ITEMS': + case 'RESET_INVALID_FILE_ITEMS': { dispatchFilesAction(action); break; } diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.tsx b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.tsx index 03ccea99c5f..f18497f694d 100644 --- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.tsx +++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/UploadView.tsx @@ -70,14 +70,15 @@ export function UploadView({ const isAddFolderDisabled = isProcessing || isProcessingComplete; const isActionExitDisabled = isProcessing; const destinationList = (location.key || '/').split('/'); - - const message = isProcessingComplete + const actionCompleteMessage = isProcessingComplete ? getActionCompleteMessage({ counts: statusCounts, }) - : invalidFiles && !isProcessing - ? getFilesValidationMessage({ invalidFiles }) : undefined; + const filesValidationMessage = + invalidFiles && !isProcessing + ? getFilesValidationMessage({ invalidFiles }) + : undefined; return (
@@ -96,7 +97,7 @@ export function UploadView({ isOverwriteToggleDisabled: isProcessing || isProcessingComplete, isOverwritingEnabled, overwriteToggleLabel, - message, + message: actionCompleteMessage ?? filesValidationMessage, statusCounts, statusDisplayCanceledLabel, statusDisplayCompletedLabel, diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/__tests__/useUploadView.spec.ts b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/__tests__/useUploadView.spec.ts index b4a19c6ea32..7fcc41f38d7 100644 --- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/__tests__/useUploadView.spec.ts +++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/__tests__/useUploadView.spec.ts @@ -104,16 +104,35 @@ describe('useUploadView', () => { }); }); - it('should dispatchStoreAction when onRemoveFile is invoked', () => { - mockUserStoreState.files = [invalidFileItem]; + it('should return invalidFiles from store', () => { + mockUserStoreState.files = { + validFiles: [], + invalidFiles: [invalidFileItem], + }; const { result } = renderHook(() => useUploadView()); + expect(result.current.invalidFiles).toEqual([invalidFileItem]); + }); + + it('should clear invalidFiles after upload is started', () => { + mockUserStoreState.files = { + validFiles: [fileItemOne], + invalidFiles: [invalidFileItem], + }; + renderHook(() => useUploadView()); + + expect(useProcessTasksSpy).toHaveBeenCalledTimes(1); + const onTaskProgressCallback = + useProcessTasksSpy.mock.calls[0][2]?.onTaskProgress; + expect(onTaskProgressCallback).toBeDefined(); + act(() => { + // @ts-expect-error Input is not needed + onTaskProgressCallback?.(); + }); + expect(dispatchStoreAction).toHaveBeenCalledTimes(1); expect(dispatchStoreAction).toHaveBeenCalledWith({ - type: 'REMOVE_FILE_ITEM', - id: invalidFileItem.id, + type: 'RESET_INVALID_FILE_ITEMS', }); - - expect(result.current.invalidFiles).toEqual([invalidFileItem]); }); it('should dispatchStoreAction when onSelectFiles is invoked with different types', () => { diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/types.ts b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/types.ts index afa539fb1a9..d7a8b88254d 100644 --- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/types.ts +++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/types.ts @@ -6,8 +6,6 @@ import { ActionViewState, } from '../types'; -export type InvalidFileReason = 'FILE_TOO_BIG'; - export interface UploadViewState extends ActionViewState { isOverwritingEnabled: boolean; onDropFiles: (files: File[]) => void; diff --git a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.ts b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.ts index 16601b78b31..8a148063a6d 100644 --- a/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.ts +++ b/packages/react-storage/src/components/StorageBrowser/views/LocationActionView/UploadView/useUploadView.ts @@ -3,43 +3,21 @@ import React from 'react'; import { uploadHandler } from '../../../actions'; import { useGetActionInput } from '../../../providers/configuration'; -import { FileItems, useStore } from '../../../providers/store'; +import { useStore } from '../../../providers/store'; import { Task, useProcessTasks } from '../../../tasks'; -import { - DEFAULT_ACTION_CONCURRENCY, - UPLOAD_FILE_SIZE_LIMIT, -} from '../constants'; +import { DEFAULT_ACTION_CONCURRENCY } from '../constants'; import { UploadViewState, UseUploadViewOptions } from './types'; import { DEFAULT_OVERWRITE_ENABLED } from './constants'; -import { isUndefined } from '@aws-amplify/ui'; export const useUploadView = ( options?: UseUploadViewOptions ): UploadViewState => { - const [invalidFiles, setInvalidFiles] = React.useState< - FileItems | undefined - >(); const { onExit: _onExit } = options ?? {}; const getInput = useGetActionInput(); const [{ files, location }, dispatchStoreAction] = useStore(); const { current, key } = location; - - const validFiles = React.useMemo( - () => - files?.filter((fileItem) => { - const { id, file } = fileItem; - if (file.size > UPLOAD_FILE_SIZE_LIMIT) { - setInvalidFiles((prev) => - isUndefined(prev) ? [fileItem] : prev.concat(fileItem) - ); - dispatchStoreAction({ type: 'REMOVE_FILE_ITEM', id }); - return false; - } - return true; - }), - [files, dispatchStoreAction] - ); + const { validFiles, invalidFiles } = files ?? {}; const [isOverwritingEnabled, setIsOverwritingEnabled] = React.useState( DEFAULT_OVERWRITE_ENABLED @@ -50,9 +28,10 @@ export const useUploadView = ( handleProcess, ] = useProcessTasks(uploadHandler, validFiles, { concurrency: DEFAULT_ACTION_CONCURRENCY, + // TODO: in reality, this should be on `onTaskStart` hood. But we don't have it today. onTaskProgress: () => { if (invalidFiles) { - setInvalidFiles(undefined); + dispatchStoreAction({ type: 'RESET_INVALID_FILE_ITEMS' }); } }, });