Skip to content

Commit b1ea48c

Browse files
authored
fix(storage-browser): move useProcessTasks concurrency option to dispatch handler (#6525)
1 parent 1002c52 commit b1ea48c

File tree

6 files changed

+60
-33
lines changed

6 files changed

+60
-33
lines changed

.changeset/nasty-months-teach.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@aws-amplify/ui-react-storage": patch
3+
---
4+
5+
fix(storage-browser): move useProcessTasks concurrency option to dispatch handler

packages/react-storage/src/components/StorageBrowser/tasks/__tests__/useProcessTasks.spec.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,7 @@ describe('useProcessTasks', () => {
9191

9292
it('handles concurrent tasks as expected', async () => {
9393
const { result } = renderHook(() =>
94-
useProcessTasks(action, {
95-
concurrency: 2,
96-
items,
97-
onTaskProgress,
98-
})
94+
useProcessTasks(action, { items, onTaskProgress })
9995
);
10096

10197
const processTasks = result.current[1];
@@ -105,7 +101,7 @@ describe('useProcessTasks', () => {
105101
expect(result.current[0].tasks[2].status).toBe('QUEUED');
106102

107103
act(() => {
108-
processTasks({ config });
104+
processTasks({ config, options: { concurrency: 2 } });
109105
});
110106

111107
expect(action).toHaveBeenCalledTimes(2);
@@ -392,13 +388,13 @@ describe('useProcessTasks', () => {
392388

393389
it('returns `error` and `message` for a failed task and provides the expected values to `onTaskError`', async () => {
394390
const { result } = renderHook(() =>
395-
useProcessTasks(action, { concurrency: 2, items, onTaskError })
391+
useProcessTasks(action, { items, onTaskError })
396392
);
397393

398394
const processTasks = result.current[1];
399395

400396
act(() => {
401-
processTasks({ config });
397+
processTasks({ config, options: { concurrency: 2 } });
402398
});
403399

404400
expect(result.current[0].tasks[0].status).toBe('PENDING');

packages/react-storage/src/components/StorageBrowser/tasks/types.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
TaskData,
44
TaskResult,
55
TaskResultStatus,
6+
TaskHandlerOptions,
67
} from '../actions';
78

89
/**
@@ -17,8 +18,6 @@ export type TaskStatus = TaskResultStatus | 'QUEUED' | 'PENDING';
1718
export type StatusCounts = Record<TaskStatus | 'TOTAL', number>;
1819

1920
export interface ProcessTasksOptions<TTask extends Task, TItems = []> {
20-
// has no impact in atomic mode
21-
concurrency?: number;
2221
items?: TItems;
2322
onTaskCancel?: (task: TTask) => void;
2423
onTaskComplete?: (task: TTask) => void;
@@ -65,9 +64,19 @@ export type UseProcessTasksState<TTask, TInput> = [
6564
HandleProcessTasks<TInput>,
6665
];
6766

67+
interface HandleTasksOptions extends TaskHandlerOptions {
68+
concurrency?: number;
69+
}
70+
71+
export interface HandleBatchTasksInput<TData extends TaskData>
72+
extends Omit<TaskHandlerInput<TData, HandleTasksOptions>, 'data'> {}
73+
74+
export interface HandleSingleTaskInput<TData extends TaskData>
75+
extends TaskHandlerInput<TData> {}
76+
6877
export type InferHandleTasksInput<
6978
TItems,
7079
TData extends TaskData,
7180
> = TItems extends NonNullable<TItems>
72-
? Omit<TaskHandlerInput<TData>, 'data'>
73-
: TaskHandlerInput<TData>;
81+
? HandleBatchTasksInput<TData>
82+
: HandleSingleTaskInput<TData>;

packages/react-storage/src/components/StorageBrowser/tasks/useProcessTasks.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import React from 'react';
22
import { isFunction } from '@aws-amplify/ui';
33

4-
import type { ActionHandler, TaskData, TaskHandlerInput } from '../actions';
4+
import type { ActionHandler, TaskData } from '../actions';
55

66
import type {
7+
HandleBatchTasksInput,
78
HandleProcessTasks,
9+
HandleSingleTaskInput,
810
InferHandleTasksInput,
911
Task,
1012
ProcessTasksOptions,
@@ -23,9 +25,10 @@ const QUEUED_TASK_BASE = {
2325
status: 'QUEUED' as const,
2426
};
2527

26-
const isTaskHandlerInput = <T extends TaskData>(
27-
input: TaskHandlerInput<T> | Omit<TaskHandlerInput<T>, 'data'>
28-
): input is TaskHandlerInput<T> => !!(input as TaskHandlerInput<T>).data;
28+
const isSingleTaskInput = <TData extends TaskData>(
29+
input: HandleSingleTaskInput<TData> | HandleBatchTasksInput<TData>
30+
): input is HandleSingleTaskInput<TData> =>
31+
!!(input as HandleSingleTaskInput<TData>).data;
2932

3033
export function useProcessTasks<
3134
TData,
@@ -38,7 +41,7 @@ export function useProcessTasks<
3841
handler: ActionHandler<TData, TValue>,
3942
options?: ProcessTasksOptions<TTask, TItems>
4043
): UseProcessTasksState<TTask, TInput> {
41-
const { concurrency, items, ...callbacks } = options ?? {};
44+
const { items, ...callbacks } = options ?? {};
4245

4346
const callbacksRef = React.useRef(callbacks);
4447

@@ -133,14 +136,14 @@ export function useProcessTasks<
133136
flush();
134137
}, [createTask, flush, updateTask, items, refreshTaskData]);
135138

136-
const processNextTask = (_input: TInput) => {
137-
const hasInputData = isTaskHandlerInput(_input);
138-
if (hasInputData) {
139+
const processTask = (_input: TInput) => {
140+
const isSingleTask = isSingleTaskInput(_input);
141+
if (isSingleTask) {
139142
createTask(_input.data);
140143
flush();
141144
}
142145

143-
const { data } = hasInputData
146+
const { data } = isSingleTask
144147
? _input
145148
: [...tasksRef.current.values()].find(
146149
({ status }) => status === 'QUEUED'
@@ -203,10 +206,10 @@ export function useProcessTasks<
203206
const task = getTask();
204207
if (task && isFunction(onTaskComplete)) onTaskComplete(task);
205208

206-
// ignore process next task for single operation inputs
207-
if (hasInputData) return;
209+
// ignore process next task for single task
210+
if (isSingleTask) return;
208211

209-
processNextTask(_input);
212+
processTask(_input);
210213
});
211214

212215
updateTask(data.id, { cancel, status: 'PENDING' });
@@ -222,14 +225,26 @@ export function useProcessTasks<
222225
return;
223226
}
224227

228+
// if single task, run `processTask` once
229+
if (isSingleTaskInput(input)) {
230+
processTask(input);
231+
return;
232+
}
233+
234+
const { concurrency, ...options } = input.options ?? {};
235+
236+
// reconstruct `input` without `concurrency`
237+
const _input = { ...input, options };
238+
239+
// for batch tasks, if no `concurrency` process tasks individually
225240
if (!concurrency) {
226-
processNextTask(input);
241+
processTask(_input);
227242
return;
228243
}
229244

230245
let count = 0;
231246
while (count < concurrency) {
232-
processNextTask(input);
247+
processTask(_input);
233248
count++;
234249
}
235250
};

packages/react-storage/src/components/StorageBrowser/useAction/__tests__/useHandler.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,10 @@ describe('useHandler', () => {
120120
dispatch();
121121

122122
expect(mockUseProcessDispatch).toHaveBeenCalledTimes(1);
123-
expect(mockUseProcessDispatch).toHaveBeenCalledWith({ config });
123+
expect(mockUseProcessDispatch).toHaveBeenCalledWith({
124+
config,
125+
options: { concurrency: DEFAULT_ACTION_CONCURRENCY },
126+
});
124127
});
125128

126129
it('provides the expected values to `useProcessTasks` when called with `options` that include `items`', () => {
@@ -143,7 +146,6 @@ describe('useHandler', () => {
143146

144147
expect(useProcessTasksMock).toHaveBeenCalledTimes(1);
145148
expect(useProcessTasksMock).toHaveBeenCalledWith(handler, {
146-
concurrency: DEFAULT_ACTION_CONCURRENCY,
147149
items: input.items,
148150
onTaskError: input.onTaskError,
149151
onTaskProgress: input.onTaskProgress,

packages/react-storage/src/components/StorageBrowser/useAction/useHandler.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,7 @@ export function useHandler<
4646
handler: THandler,
4747
options?: UseHandlerOptionsWithItems<TTask> | UseHandlerOptions<TTask>
4848
): HandleTasksState<TTask> | HandleTaskState<TTask> {
49-
const [state, handleProcessing] = useProcessTasks(handler, {
50-
...options,
51-
concurrency: DEFAULT_ACTION_CONCURRENCY,
52-
});
49+
const [state, handleProcessing] = useProcessTasks(handler, options);
5350
const getConfig = useGetActionInput();
5451

5552
const { reset, isProcessing, tasks, ...rest } = state;
@@ -64,7 +61,10 @@ export function useHandler<
6461

6562
handleProcessing({
6663
config,
67-
...(hasData ? { data: input.data } : undefined),
64+
...(hasData
65+
? { data: input.data }
66+
: // if no `data` provided, provide `concurrency` to `options`
67+
{ options: { concurrency: DEFAULT_ACTION_CONCURRENCY } }),
6868
});
6969
},
7070
[getConfig, handleProcessing, reset]

0 commit comments

Comments
 (0)