Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

<p align="center">English | <a href="./README.zh-CN.md">简体中文</a></p>


## Highlights

- Supports Ajax uploads with progress, headers, credentials, and custom request overrides.
Expand Down Expand Up @@ -98,9 +97,10 @@ Then open `http://localhost:8000`.

### Methods

| Name | Type | Description |
| ------- | ------------------------- | ----------------------- |
| `abort` | `(file: RcFile) => void` | Abort an active upload. |
| Name | Type | Description |
| ------------- | ------------------------ | ------------------------------------ |
| `abort` | `(file: RcFile) => void` | Abort an active upload. |
| `retryUpload` | `(file: RcFile) => void` | Retry an upload for a specific file. |

## Development

Expand Down
8 changes: 4 additions & 4 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

<p align="center"><a href="./README.md">English</a> | 简体中文</p>


## 特性

- 支持带进度、请求头、凭证和自定义请求覆盖的 Ajax 上传。
Expand Down Expand Up @@ -98,9 +97,10 @@ npm start

### 方法

| 名称 | 类型 | 说明 |
| ------- | ------------------------- | ----------------------- |
| `abort` | `(file: RcFile) => void` | 中止进行中的上传。 |
| 名称 | 类型 | 说明 |
| ------------- | ------------------------ | -------------------- |
| `abort` | `(file: RcFile) => void` | 中止进行中的上传。 |
| `retryUpload` | `(file: RcFile) => void` | 重试特定文件的上传。 |

## 本地开发

Expand Down
70 changes: 57 additions & 13 deletions src/AjaxUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,17 +179,21 @@ class AjaxUploader extends Component<UploadProps> {
});

// Batch upload files
Promise.all(postFiles).then(fileList => {
const { onBatchStart } = this.props;

onBatchStart?.(fileList.map(({ origin, parsedFile }) => ({ file: origin, parsedFile })));

fileList
.filter(file => file.parsedFile !== null)
.forEach(file => {
this.post(file);
});
});
Promise.all(postFiles)
.then(fileList => {
const { onBatchStart } = this.props;

onBatchStart?.(fileList.map(({ origin, parsedFile }) => ({ file: origin, parsedFile })));

fileList
.filter(file => file.parsedFile !== null)
.forEach(file => {
this.post(file);
});
})
.catch(() => {
// Error already handled in processFile, this catch is to prevent unhandled rejection
});
};

/**
Expand Down Expand Up @@ -220,20 +224,48 @@ class AjaxUploader extends Component<UploadProps> {
const { action } = this.props;
let mergedAction: string;
if (typeof action === 'function') {
mergedAction = await action(file);
try {
mergedAction = await action(file);
} catch {
transformedFile = false;
}
} else {
mergedAction = action;
}

// Early return if action rejected
if (transformedFile === false) {
return {
origin: file,
parsedFile: null,
action: null,
data: null,
};
}

// Get latest data
const { data } = this.props;
let mergedData: Record<string, unknown>;
if (typeof data === 'function') {
mergedData = await data(file);
try {
mergedData = await data(file);
} catch {
transformedFile = false;
}
} else {
mergedData = data;
}

// Early return if data rejected
if (transformedFile === false) {
return {
origin: file,
parsedFile: null,
action: null,
data: null,
};
}

const parsedData =
// string type is from legacy `transformFile`.
// Not sure if this will work since no related test case works with it
Expand Down Expand Up @@ -301,6 +333,18 @@ class AjaxUploader extends Component<UploadProps> {
this.reqs[uid] = request(requestOption, { defaultRequest });
}

retryUpload = (originFile: RcFile) => {
Comment thread
EmilyyyLiu marked this conversation as resolved.
this.processFile(originFile, [originFile])
.then(fileInfo => {
if (fileInfo.parsedFile) {
this.post(fileInfo);
}
})
.catch(() => {
// Error already handled in processFile for action/data rejection
});
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.

reset() {
this.setState({
uid: getUid(),
Expand Down
4 changes: 4 additions & 0 deletions src/Upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class Upload extends Component<UploadProps> {
this.uploader.abort(file);
}

retryUpload(file: RcFile) {
this.uploader.retryUpload(file);
}

saveUploader = (node: AjaxUpload) => {
this.uploader = node;
};
Expand Down
89 changes: 88 additions & 1 deletion tests/uploader.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { fireEvent, render } from '@testing-library/react';
import React from 'react';
import sinon from 'sinon';
import { format } from 'util';
import Upload, { type UploadProps } from '../src';
import Upload, { type UploadProps, type RcFile } from '../src';

const sleep = (timeout = 500) => new Promise(resolve => setTimeout(resolve, timeout));

Expand Down Expand Up @@ -253,6 +253,93 @@ describe('uploader', () => {
}, 100);
});

it('retryUpload should make new request', done => {
const uploadRef = React.createRef<any>();
render(<Upload ref={uploadRef} action="/test" />);

const file = {
uid: 'rc-upload-retry-test',
name: 'retry.png',
toString() {
return this.name;
},
};
const files = [file];
(files as any).item = (i: number) => files[i];

const initialRequestCount = requests.length;

uploadRef.current.retryUpload(file as RcFile);

setTimeout(() => {
expect(requests.length).toBe(initialRequestCount + 1);
// Verify the request uses the correct file uid
expect(requests[initialRequestCount].url).toBe('/test');
done();
}, 100);
});

it('retryUpload should not make request when async action rejects', done => {
const uploadRef = React.createRef<any>();
render(
<Upload
ref={uploadRef}
action={async () => {
throw new Error('action error');
}}
/>,
);

const file = {
uid: 'rc-upload-retry-action-reject',
name: 'retry.png',
toString() {
return this.name;
},
};

const initialRequestCount = requests.length;

uploadRef.current.retryUpload(file as RcFile);

setTimeout(() => {
// Should not make a new request when action rejects
expect(requests.length).toBe(initialRequestCount);
done();
}, 100);
});

it('retryUpload should not make request when async data rejects', done => {
const uploadRef = React.createRef<any>();
render(
<Upload
ref={uploadRef}
action="/test"
data={() => {
throw new Error('data error');
}}
/>,
);

const file = {
uid: 'rc-upload-retry-data-reject',
name: 'retry.png',
toString() {
return this.name;
},
};

const initialRequestCount = requests.length;

uploadRef.current.retryUpload(file as RcFile);

setTimeout(() => {
// Should not make a new request when data rejects
expect(requests.length).toBe(initialRequestCount);
done();
}, 100);
});

it('drag to upload', done => {
const input = uploader.container.querySelector('input')!;

Expand Down
Loading