Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React storage/cancel upload from process file #5179

Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5fce159
Get `yarn setup` to run locally
raystorm Apr 24, 2024
7957bf1
Update `CONTRIBUTING.md` files
raystorm Apr 24, 2024
9af00ac
Ability to cancel uploads in processFile - implements aws-amplify/amp…
raystorm Apr 24, 2024
e2cac8e
Add Example to locally test rejecting an upload in ProcessFile.
raystorm Apr 24, 2024
1639c38
Remove Environment changes
raystorm Apr 24, 2024
29afbdb
Merge remote-tracking branch 'upstream/main' into react-storage/cance…
raystorm Apr 25, 2024
db02fa7
Merge in Upstream Changes to fix Merge Conflicts.
raystorm May 10, 2024
92400ec
Merge branch 'main' into react-storage/cancel-upload-from-processFile
calebpollman May 23, 2024
558098e
Merge branch 'main' into react-storage/cancel-upload-from-processFile
calebpollman Jun 4, 2024
7f96d37
Initial PR Updates
raystorm Jun 19, 2024
1b54c48
Update packages/react/CONTRIBUTING.md
raystorm Jun 30, 2024
a54b074
Update packages/react/CONTRIBUTING.md
raystorm Jun 30, 2024
b75e330
Update packages/react/CONTRIBUTING.md
raystorm Jun 30, 2024
742e226
More PR Updates
raystorm Jun 30, 2024
f5f5600
Update `CONTRIBUTING.md` files
raystorm Apr 24, 2024
eb8e738
Ability to cancel uploads in processFile - implements aws-amplify/amp…
raystorm Apr 24, 2024
977a483
Add Example to locally test rejecting an upload in ProcessFile.
raystorm Apr 24, 2024
49101e9
Remove Environment changes
raystorm Apr 24, 2024
cafd27d
Initial PR Updates
raystorm Jun 19, 2024
dc4bb15
More PR Updates
raystorm Jun 30, 2024
1ef9f41
Fix merge conflicts and Notify Users on `processFile` errors
raystorm Aug 24, 2024
46fec56
Merge remote-tracking branch 'upstream/main' into react-storage/cance…
raystorm Aug 25, 2024
af1f328
Merge remote-tracking branch 'origin/react-storage/cancel-upload-from…
raystorm Aug 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/wet-steaks-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@aws-amplify/ui-react-storage": patch
---

react-storage ability to cancel uploads from ProcessFile
This allows a user to return a rejected promise from `ProcessFile()` to cancel an in process upload,
before any data is sent to S3.

2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ You should open an issue to discuss your pull request, unless it's a trivial cha
1. Fork & Clone this repo (Make sure to disable associated GitHub Actions. In fork go to Settings > Actions > General > Disable actions > save)
1. [`nvm install`](https://github.com/nvm-sh/nvm)
1. [`nvm use`](https://github.com/nvm-sh/nvm)
1. `yarn setup`
1. `yarn setup` - _NOTE:_ if Cypress Fails to download during this step, delete `yarn.lock` and try again.
calebpollman marked this conversation as resolved.
Show resolved Hide resolved
1. Within your fork, create a new branch based on the issue you're addressing -- `git checkout -b angular/remove-browser-module`
1. Once your work is committed, validate your changes according to [local development guides](#local-development-guides).
1. Push your branch with `git push origin -u`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import awsExports from '@environments/storage/file-uploader/src/aws-exports';
export default awsExports;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Amplify } from 'aws-amplify';
import { withAuthenticator } from '@aws-amplify/ui-react';
import {
StorageManager,
StorageManagerProps,
} from '@aws-amplify/ui-react-storage';
import '@aws-amplify/ui-react/styles.css';

import awsExports from './aws-exports';
Amplify.configure(awsExports);

const processFile: StorageManagerProps['processFile'] = async ({ file }) => {
return Promise.reject({
error: 'This File should not be uploaded.',
key: file.name,
});
calebpollman marked this conversation as resolved.
Show resolved Hide resolved
};

export function StorageManagerExample() {
return (
<StorageManager
acceptedFileTypes={['image/*']}
accessLevel="private"
maxFileCount={3}
showThumbnails={true}
processFile={processFile}
/>
);
}
export default withAuthenticator(StorageManagerExample);
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ const StorageManagerBase = React.forwardRef(function StorageManager(
setUploadingFile,
setUploadProgress,
setUploadSuccess,
removeUpload,
processFile,
path,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@ const mockSetUploadProgress = jest.fn();
const mockSetUploadSuccess = jest.fn();
const mockOnUploadError = jest.fn();
const mockOnUploadStart = jest.fn();
const mockRemoveUpload = jest.fn();
const props: Omit<UseUploadFilesProps, 'files'> = {
accessLevel: 'guest',
maxFileCount: 2,
setUploadingFile: mockSetUploadingFile,
setUploadProgress: mockSetUploadProgress,
setUploadSuccess: mockSetUploadSuccess,
removeUpload: mockRemoveUpload,
isResumable: false,
onUploadError: mockOnUploadError,
onUploadStart: mockOnUploadStart,
};
Expand Down Expand Up @@ -190,6 +193,32 @@ describe('useUploadFiles', () => {
});
});

it('should remove upload after processFile promise rejects', async () => {
const processFile: StorageManagerProps['processFile'] = ({ file }) =>
Promise.reject({ file, key: 'test.png', error: 'forced test error' });

const { waitForNextUpdate } = renderHook(() =>
useUploadFiles({
...props,
isResumable: true,
processFile,
files: [mockQueuedFile],
})
);

waitForNextUpdate();

await waitFor(() => {
expect(mockOnUploadError).toHaveBeenCalledWith('forced test error', {
key: 'test.png',
});
});

await waitFor(() => {
expect(mockRemoveUpload).toHaveBeenCalled();
});
});
Copy link
Author

Choose a reason for hiding this comment

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

@calebpollman, With your comments on my example is this Unit Test incorrect?
Should I only be testing a thrown Exception? or is that a second test case?


it('prepends valid provided `path` to `processedKey`', async () => {
const path = 'test-path/';
const { waitForNextUpdate } = renderHook(() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import * as React from 'react';
import { TransferProgressEvent } from 'aws-amplify/storage';
import { isFunction } from '@aws-amplify/ui';

import { PathCallback, uploadFile } from '../../utils';
import { getInput } from '../../utils';
import { PathCallback, getInput } from '../../utils';
import { uploadFile } from '../../utils/uploadFile';
import { FileStatus } from '../../types';
import { StorageManagerProps } from '../../types';
import { UseStorageManager } from '../useStorageManager';
Expand All @@ -21,7 +21,11 @@ export interface UseUploadFilesProps
>,
Pick<
UseStorageManager,
'setUploadingFile' | 'setUploadProgress' | 'setUploadSuccess' | 'files'
| 'setUploadingFile'
| 'setUploadProgress'
| 'setUploadSuccess'
| 'files'
| 'removeUpload'
> {
accessLevel?: StorageManagerProps['accessLevel'];
path?: string | PathCallback;
Expand All @@ -35,6 +39,7 @@ export function useUploadFiles({
setUploadingFile,
setUploadSuccess,
onUploadError,
removeUpload,
onUploadSuccess,
onUploadStart,
maxFileCount,
Expand Down Expand Up @@ -71,8 +76,10 @@ export function useUploadFiles({
onProgress,
path,
processFile,
id,
onUploadError,
removeUpload,
});

uploadFile({
input,
onComplete: (event) => {
Expand Down Expand Up @@ -106,6 +113,7 @@ export function useUploadFiles({
setUploadProgress,
setUploadingFile,
onUploadError,
removeUpload,
onUploadSuccess,
onUploadStart,
maxFileCount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type DefaultFile = Pick<StorageFile, 'key'>;
export interface ProcessFileParams extends Record<string, any> {
file: File;
key: string;
error?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs to be removed, processFile should throw the underlying exception and be captured by the caller

}

export type ProcessFile = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const inputBase: Omit<GetInputParams, 'path' | 'accessLevel'> = {
key,
onProgress,
processFile: undefined,
id: '',
onUploadError: undefined,
removeUpload: () => {},
};
const pathStringInput: GetInputParams = {
...inputBase,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { fetchAuthSession } from 'aws-amplify/auth';
import { StorageAccessLevel } from '@aws-amplify/core';
import { UploadDataWithPathInput, UploadDataInput } from 'aws-amplify/storage';

import { isString, isTypedFunction } from '@aws-amplify/ui';
import { isFunction, isString, isTypedFunction } from '@aws-amplify/ui';

import { ProcessFile } from '../types';
import { ProcessFile, ProcessFileParams, StorageManagerProps } from '../types';
import { resolveFile } from './resolveFile';
import { PathCallback, PathInput } from './uploadFile';
import { UseStorageManager } from '../hooks';

export interface GetInputParams {
accessLevel: StorageAccessLevel | undefined;
Expand All @@ -15,6 +16,9 @@ export interface GetInputParams {
onProgress: NonNullable<UploadDataWithPathInput['options']>['onProgress'];
path: string | PathCallback | undefined;
processFile: ProcessFile | undefined;
id: string;
onUploadError: StorageManagerProps['onUploadError'];
removeUpload: UseStorageManager['removeUpload'];
}

export const getInput = ({
Expand All @@ -24,6 +28,9 @@ export const getInput = ({
onProgress,
path,
processFile,
id,
onUploadError,
removeUpload,
}: GetInputParams) => {
return async (): Promise<PathInput | UploadDataInput> => {
const hasCallbackPath = isTypedFunction<PathCallback>(path);
Expand All @@ -35,7 +42,17 @@ export const getInput = ({
file: data,
key: fileKey,
...rest
} = await resolveFile({ file, key, processFile });
} = await resolveFile({ file, key, processFile }).catch(
(result: ProcessFileParams) => {
const { key, error } = result;
if (isFunction(onUploadError)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Because the caught exception here is from resolveFile and not an upload error, this usage of onUploadError is incorrect and needs to be removed

Copy link
Author

Choose a reason for hiding this comment

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

This has been commented out as incorrect, with a note, for a handler that might need to be added. Let me know if I should remove the code and the comment.

//TODO: localize this error.
onUploadError(error ?? `Error processing: ${key}`, { key: key });
}
removeUpload({ id });
return result;
calebpollman marked this conversation as resolved.
Show resolved Hide resolved
}
);

const contentType = file.type || 'binary/octet-stream';

Expand Down
30 changes: 23 additions & 7 deletions packages/react/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,29 @@
## Getting Started

1. Navigate to the _root_ of your local clone of [aws-amplify/amplify-ui](https://github.com/aws-amplify/amplify-ui)
1. Run `yarn setup`
1. [`nvm install`](https://github.com/nvm-sh/nvm)
1. [`nvm use`](https://github.com/nvm-sh/nvm)
calebpollman marked this conversation as resolved.
Show resolved Hide resolved
1. Run `yarn setup` - _NOTE:_ if Cypress Fails to download during this step, delete `yarn.lock` and try again.
Copy link
Contributor

Choose a reason for hiding this comment

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

As noted previously, regenerating the yarn.lock can lead to unintended side effects. Please remove this change

1. Run `yarn react dev`

This will start building `@aws-amplify/ui-react` in watch mode. To test your changes, you can utilize [`examples/next`](../../examples/next) to run examples on `next.js`. Please see examples [README](../../examples/README.md) and e2e [README](../e2e/README.md#contributing) to get started.
This will start building `@aws-amplify/ui-react` in watch mode.
To test your changes, you can utilize [`examples/next`](../../examples/next)
to run examples on `next.js`.
Please see examples [README](../../examples/README.md) and e2e [README](../e2e/README.md#contributing) to get started.
raystorm marked this conversation as resolved.
Show resolved Hide resolved

## Dependencies

`@aws-amplify/ui-react` depends on [`@aws-amplify/ui`](../ui) for theming, state management, and translation logic. If you're looking for change in these, please refer to `@aws-amplify/ui` [README](../ui/README.md).
`@aws-amplify/ui-react` depends on [`@aws-amplify/ui`](../ui) for theming,
state management, and translation logic.
If you're looking for change in these,
please refer to `@aws-amplify/ui` [README](../ui/README.md).
raystorm marked this conversation as resolved.
Show resolved Hide resolved

## Code Standards

### Imports

Separate all imports into organized, alphabetical blocks of `third_party → internal → local` for easier reading.
Separate all imports into organized,
alphabetical blocks of `third_party → internal → local` for easier reading.
Comment on lines +26 to +27
Copy link
Contributor

Choose a reason for hiding this comment

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

These lines should be a single sentence:

Suggested change
Separate all imports into organized,
alphabetical blocks of `third_party → internal → local` for easier reading.
Separate all imports into organized, alphabetical blocks of `third_party → internal → local` for easier reading.


```js
import { isEmpty } from 'lodash/isEmpty';
Expand All @@ -28,7 +37,8 @@ import { Button } from './primitives/Button';
import { THIS_ENUM } from './utils/types';
```

Do **NOT** use implicit paths like below. This can lead to circular dependencies unintentionally which is bad for tree shaking.
Do **NOT** use implicit paths like below.
This can lead to circular dependencies unintentionally which is bad for tree shaking.
Comment on lines +39 to +40
Copy link
Contributor

Choose a reason for hiding this comment

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

These lines should be grouped in a single paragraph:

Suggested change
Do **NOT** use implicit paths like below.
This can lead to circular dependencies unintentionally which is bad for tree shaking.
Do **NOT** use implicit paths like below. This can lead to circular dependencies unintentionally which is bad for tree shaking.


```js
import { Flex, Heading } from '../../..';
Expand All @@ -48,13 +58,19 @@ import { Heading } from '../../../primitives/Heading';

### Others

Do **NOT** introduce any [side effects](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free) unless you have to with rational reasons. A "side effect" is defined as code that performs a special behavior when imported, other than exposing one or more exports. An example of this are polyfills, which affect the global scope and usually do not provide an export. For example:
Do **NOT** introduce any [side effects](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free) unless you have to with rational reasons.
A "side effect" is defined as code that performs a special behavior when imported,
other than exposing one or more exports.
An example of this are polyfills, which affect the global scope and usually do not provide an export.
For example:
Comment on lines +60 to +64
Copy link
Contributor

Choose a reason for hiding this comment

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

These lines should be a single paragraph:

Suggested change
Do **NOT** introduce any [side effects](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free) unless you have to with rational reasons.
A "side effect" is defined as code that performs a special behavior when imported,
other than exposing one or more exports.
An example of this are polyfills, which affect the global scope and usually do not provide an export.
For example:
Do **NOT** introduce any [side effects](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free) unless you have to with rational reasons. A "side effect" is defined as code that performs a special behavior when imported, other than exposing one or more exports. An example of this are polyfills, which affect the global scope and usually do not provide an export. For example:


```js
import './polyfill';
```

Any imported file is subject to tree shaking, that means CSS file needs to be added to the side effect list so it will not be unintentionally dropped:
Any imported file is subject to tree shaking,
that means CSS file needs to be added to the side effect list so it will not be
unintentionally dropped:
raystorm marked this conversation as resolved.
Show resolved Hide resolved

```js
import './style.css';
Expand Down
2 changes: 1 addition & 1 deletion turbo.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://turborepo.org/schema.json",
"baseBranch": "origin/main",
// "baseBranch": "origin/main",
Copy link
Contributor

Choose a reason for hiding this comment

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

Please uncomment this line

Copy link
Author

Choose a reason for hiding this comment

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

Turbo didn't work for me with that line uncommented.

"pipeline": {
"build": {
"outputs": [".next/**", "dist/**"],
Expand Down