feat: prevent download large files

This commit is contained in:
Leangseu Kim
2022-04-19 14:51:04 -04:00
committed by leangseu-edx
parent 7e9eab24b0
commit 74423bf359
5 changed files with 198 additions and 12 deletions

View File

@@ -2,11 +2,13 @@ import React from 'react';
import PropTypes from 'prop-types';
import {
Card, Collapsible, Icon, DataTable,
Card, Collapsible, Icon, DataTable, Button,
} from '@edx/paragon';
import { ArrowDropDown, ArrowDropUp } from '@edx/paragon/icons';
import { ArrowDropDown, ArrowDropUp, WarningFilled } from '@edx/paragon/icons';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { downloadAllLimit, downloadSingleLimit } from 'data/constants/files';
import FileNameCell from './components/FileNameCell';
import FileExtensionCell from './components/FileExtensionCell';
import FilePopoverCell from './components/FilePopoverCell';
@@ -19,7 +21,17 @@ import messages from './messages';
*/
export class SubmissionFiles extends React.Component {
get title() {
return `Submission Files (${this.props.files.length})`;
return `${this.props.intl.formatMessage(messages.submissionFiles)} (${this.props.files.length})`;
}
get canDownload() {
let totalFileSize = 0;
const exceedFileSize = this.props.files.some(file => {
totalFileSize += file.size;
return file.size > downloadSingleLimit;
});
return !exceedFileSize && totalFileSize < downloadAllLimit;
}
render() {
@@ -70,7 +82,15 @@ export class SubmissionFiles extends React.Component {
</Collapsible.Body>
</Collapsible.Advanced>
<Card.Footer className="text-right">
<FileDownload files={files} />
{
this.canDownload ? <FileDownload files={files} /> : (
<div>
<Icon className="d-inline-block align-middle" src={WarningFilled} />
<span className="exceed-download-text"> {intl.formatMessage(messages.exceedFileSize)} </span>
<Button disabled>{intl.formatMessage(messages.downloadFiles)}</Button>
</div>
)
}
</Card.Footer>
</>
) : (

View File

@@ -1,8 +1,11 @@
import React from 'react';
import { shallow } from 'enzyme';
import { downloadAllLimit, downloadSingleLimit } from 'data/constants/files';
import { formatMessage } from 'testUtils';
import { SubmissionFiles } from './SubmissionFiles';
import messages from './messages';
jest.mock('./components/FileNameCell', () => jest.fn().mockName('FileNameCell'));
jest.mock('./components/FileExtensionCell', () => jest.fn().mockName('FileExtensionCell'));
@@ -16,25 +19,34 @@ describe('SubmissionFiles', () => {
name: 'some file name.jpg',
description: 'description for the file',
downloadURL: '/valid-url-wink-wink',
size: 0,
},
{
name: 'file number 2.jpg',
description: 'description for this file',
downloadURL: '/url-2',
size: 0,
},
],
};
let el;
beforeAll(() => {
el = shallow(<SubmissionFiles intl={{ formatMessage }} />);
beforeEach(() => {
el = shallow(<SubmissionFiles intl={{ formatMessage }} {...props} />);
});
describe('snapshot', () => {
test('files does not exist', () => {
test('files existed for props', () => {
expect(el).toMatchSnapshot();
});
test('files exited for props', () => {
el.setProps({ ...props });
test('files does not exist', () => {
el.setProps({ files: [] });
expect(el).toMatchSnapshot();
});
test('files size exceed', () => {
const files = props.files.map(file => ({ ...file, size: downloadSingleLimit + 1 }));
el.setProps({ files });
expect(el).toMatchSnapshot();
});
});
@@ -43,12 +55,47 @@ describe('SubmissionFiles', () => {
test('title', () => {
const titleEl = el.find('.submission-files-title>h3');
expect(titleEl.text()).toEqual(
`Submission Files (${props.files.length})`,
`${formatMessage(messages.submissionFiles)} (${props.files.length})`,
);
expect(el.instance().title).toEqual(
`Submission Files (${props.files.length})`,
`${formatMessage(messages.submissionFiles)} (${props.files.length})`,
);
});
describe('canDownload', () => {
test('normal file size', () => {
expect(el.instance().canDownload).toEqual(true);
});
test('one of the file exceed the limit', () => {
const oneFileExceed = [{ ...props.files[0], size: downloadSingleLimit + 1 }, props.files[1]];
oneFileExceed.forEach(file => expect(file.size < downloadAllLimit).toEqual(true));
el.setProps({ files: oneFileExceed });
expect(el.instance().canDownload).toEqual(false);
const warningEl = el.find('span.exceed-download-text');
expect(warningEl.text().trim()).toEqual(formatMessage(messages.exceedFileSize));
});
test('total file size exceed the limit', () => {
const length = 20;
const totalFilesExceed = new Array(length).fill({
name: 'some file name.jpg',
description: 'description for the file',
downloadURL: '/valid-url-wink-wink',
size: (downloadAllLimit + 1) / length,
});
totalFilesExceed.forEach(file => {
expect(file.size < downloadAllLimit).toEqual(true);
expect(file.size < downloadSingleLimit).toEqual(true);
});
el.setProps({ files: totalFilesExceed });
expect(el.instance().canDownload).toEqual(false);
});
});
});
});
});

View File

@@ -14,7 +14,7 @@ exports[`SubmissionFiles component snapshot files does not exist 1`] = `
</Card>
`;
exports[`SubmissionFiles component snapshot files exited for props 1`] = `
exports[`SubmissionFiles component snapshot files existed for props 1`] = `
<Card
className="submission-files"
>
@@ -75,11 +75,13 @@ exports[`SubmissionFiles component snapshot files exited for props 1`] = `
"description": "description for the file",
"downloadURL": "/valid-url-wink-wink",
"name": "some file name.jpg",
"size": 0,
},
Object {
"description": "description for this file",
"downloadURL": "/url-2",
"name": "file number 2.jpg",
"size": 0,
},
]
}
@@ -100,11 +102,13 @@ exports[`SubmissionFiles component snapshot files exited for props 1`] = `
"description": "description for the file",
"downloadURL": "/valid-url-wink-wink",
"name": "some file name.jpg",
"size": 0,
},
Object {
"description": "description for this file",
"downloadURL": "/url-2",
"name": "file number 2.jpg",
"size": 0,
},
]
}
@@ -112,3 +116,105 @@ exports[`SubmissionFiles component snapshot files exited for props 1`] = `
</Card.Footer>
</Card>
`;
exports[`SubmissionFiles component snapshot files size exceed 1`] = `
<Card
className="submission-files"
>
<Collapsible.Advanced
defaultOpen={true}
>
<Collapsible.Trigger
className="submission-files-title"
>
<h3>
Submission Files (2)
</h3>
<Collapsible.Visible
whenClosed={true}
>
<Icon
src={[MockFunction icons.ArrowDropDown]}
/>
</Collapsible.Visible>
<Collapsible.Visible
whenOpen={true}
>
<Icon
src={[MockFunction icons.ArrowDropUp]}
/>
</Collapsible.Visible>
</Collapsible.Trigger>
<Collapsible.Body
className="submission-files-body"
>
<div
className="submission-files-table"
>
<DataTable
columns={
Array [
Object {
"Cell": [MockFunction FileNameCell],
"Header": "Name",
"accessor": "name",
},
Object {
"Cell": [MockFunction FileExtensionCell],
"Header": "File Extension",
"accessor": "name",
"id": "extension",
},
Object {
"Cell": [MockFunction FilePopoverCell],
"Header": "File Metadata",
"accessor": "",
},
]
}
data={
Array [
Object {
"description": "description for the file",
"downloadURL": "/valid-url-wink-wink",
"name": "some file name.jpg",
"size": 1610612737,
},
Object {
"description": "description for this file",
"downloadURL": "/url-2",
"name": "file number 2.jpg",
"size": 1610612737,
},
]
}
itemCount={2}
>
<DataTable.Table />
</DataTable>
</div>
</Collapsible.Body>
</Collapsible.Advanced>
<Card.Footer
className="text-right"
>
<div>
<Icon
className="d-inline-block align-middle"
/>
<span
className="exceed-download-text"
>
Exceeded the allow download size
</span>
<Button
disabled={true}
>
Download files
</Button>
</div>
</Card.Footer>
</Card>
`;

View File

@@ -36,6 +36,16 @@ const messages = defineMessages({
defaultMessage: 'Retry download',
description: 'Download files failed state label',
},
submissionFiles: {
id: 'ora-grading.ResponseDisplay.SubmissionFiles.submissionFile',
defaultMessage: 'Submission Files',
description: 'Total submission files',
},
exceedFileSize: {
id: 'ora-grading.ResponseDisplay.SubmissionFiles.fileSizeExceed',
defaultMessage: 'Exceeded the allow download size',
description: 'Exceed the allow download size error message',
},
});
export default messages;

View File

@@ -14,4 +14,7 @@ export const FileTypes = StrictDict({
svg: 'svg',
});
export const downloadSingleLimit = 1610612736; // 1.5GB
export const downloadAllLimit = 10737418240; // 10GB
export default FileTypes;