Skip to content

Commit 74423bf

Browse files
committed
feat: prevent download large files
1 parent 7e9eab2 commit 74423bf

File tree

5 files changed

+197
-11
lines changed

5 files changed

+197
-11
lines changed

src/containers/ResponseDisplay/SubmissionFiles.jsx

+24-4
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import React from 'react';
22
import PropTypes from 'prop-types';
33

44
import {
5-
Card, Collapsible, Icon, DataTable,
5+
Card, Collapsible, Icon, DataTable, Button,
66
} from '@edx/paragon';
7-
import { ArrowDropDown, ArrowDropUp } from '@edx/paragon/icons';
7+
import { ArrowDropDown, ArrowDropUp, WarningFilled } from '@edx/paragon/icons';
88
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
99

10+
import { downloadAllLimit, downloadSingleLimit } from 'data/constants/files';
11+
1012
import FileNameCell from './components/FileNameCell';
1113
import FileExtensionCell from './components/FileExtensionCell';
1214
import FilePopoverCell from './components/FilePopoverCell';
@@ -19,7 +21,17 @@ import messages from './messages';
1921
*/
2022
export class SubmissionFiles extends React.Component {
2123
get title() {
22-
return `Submission Files (${this.props.files.length})`;
24+
return `${this.props.intl.formatMessage(messages.submissionFiles)} (${this.props.files.length})`;
25+
}
26+
27+
get canDownload() {
28+
let totalFileSize = 0;
29+
const exceedFileSize = this.props.files.some(file => {
30+
totalFileSize += file.size;
31+
return file.size > downloadSingleLimit;
32+
});
33+
34+
return !exceedFileSize && totalFileSize < downloadAllLimit;
2335
}
2436

2537
render() {
@@ -70,7 +82,15 @@ export class SubmissionFiles extends React.Component {
7082
</Collapsible.Body>
7183
</Collapsible.Advanced>
7284
<Card.Footer className="text-right">
73-
<FileDownload files={files} />
85+
{
86+
this.canDownload ? <FileDownload files={files} /> : (
87+
<div>
88+
<Icon className="d-inline-block align-middle" src={WarningFilled} />
89+
<span className="exceed-download-text"> {intl.formatMessage(messages.exceedFileSize)} </span>
90+
<Button disabled>{intl.formatMessage(messages.downloadFiles)}</Button>
91+
</div>
92+
)
93+
}
7494
</Card.Footer>
7595
</>
7696
) : (

src/containers/ResponseDisplay/SubmissionFiles.test.jsx

+53-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import React from 'react';
22
import { shallow } from 'enzyme';
33

4+
import { downloadAllLimit, downloadSingleLimit } from 'data/constants/files';
5+
46
import { formatMessage } from 'testUtils';
57
import { SubmissionFiles } from './SubmissionFiles';
8+
import messages from './messages';
69

710
jest.mock('./components/FileNameCell', () => jest.fn().mockName('FileNameCell'));
811
jest.mock('./components/FileExtensionCell', () => jest.fn().mockName('FileExtensionCell'));
@@ -16,25 +19,34 @@ describe('SubmissionFiles', () => {
1619
name: 'some file name.jpg',
1720
description: 'description for the file',
1821
downloadURL: '/valid-url-wink-wink',
22+
size: 0,
1923
},
2024
{
2125
name: 'file number 2.jpg',
2226
description: 'description for this file',
2327
downloadURL: '/url-2',
28+
size: 0,
2429
},
2530
],
2631
};
2732
let el;
28-
beforeAll(() => {
29-
el = shallow(<SubmissionFiles intl={{ formatMessage }} />);
33+
beforeEach(() => {
34+
el = shallow(<SubmissionFiles intl={{ formatMessage }} {...props} />);
3035
});
3136

3237
describe('snapshot', () => {
38+
test('files existed for props', () => {
39+
expect(el).toMatchSnapshot();
40+
});
41+
3342
test('files does not exist', () => {
43+
el.setProps({ files: [] });
44+
3445
expect(el).toMatchSnapshot();
3546
});
36-
test('files exited for props', () => {
37-
el.setProps({ ...props });
47+
test('files size exceed', () => {
48+
const files = props.files.map(file => ({ ...file, size: downloadSingleLimit + 1 }));
49+
el.setProps({ files });
3850
expect(el).toMatchSnapshot();
3951
});
4052
});
@@ -43,12 +55,47 @@ describe('SubmissionFiles', () => {
4355
test('title', () => {
4456
const titleEl = el.find('.submission-files-title>h3');
4557
expect(titleEl.text()).toEqual(
46-
`Submission Files (${props.files.length})`,
58+
`${formatMessage(messages.submissionFiles)} (${props.files.length})`,
4759
);
4860
expect(el.instance().title).toEqual(
49-
`Submission Files (${props.files.length})`,
61+
`${formatMessage(messages.submissionFiles)} (${props.files.length})`,
5062
);
5163
});
64+
65+
describe('canDownload', () => {
66+
test('normal file size', () => {
67+
expect(el.instance().canDownload).toEqual(true);
68+
});
69+
70+
test('one of the file exceed the limit', () => {
71+
const oneFileExceed = [{ ...props.files[0], size: downloadSingleLimit + 1 }, props.files[1]];
72+
73+
oneFileExceed.forEach(file => expect(file.size < downloadAllLimit).toEqual(true));
74+
75+
el.setProps({ files: oneFileExceed });
76+
expect(el.instance().canDownload).toEqual(false);
77+
78+
const warningEl = el.find('span.exceed-download-text');
79+
expect(warningEl.text().trim()).toEqual(formatMessage(messages.exceedFileSize));
80+
});
81+
82+
test('total file size exceed the limit', () => {
83+
const length = 20;
84+
const totalFilesExceed = new Array(length).fill({
85+
name: 'some file name.jpg',
86+
description: 'description for the file',
87+
downloadURL: '/valid-url-wink-wink',
88+
size: (downloadAllLimit + 1) / length,
89+
});
90+
totalFilesExceed.forEach(file => {
91+
expect(file.size < downloadAllLimit).toEqual(true);
92+
expect(file.size < downloadSingleLimit).toEqual(true);
93+
});
94+
95+
el.setProps({ files: totalFilesExceed });
96+
expect(el.instance().canDownload).toEqual(false);
97+
});
98+
});
5299
});
53100
});
54101
});

src/containers/ResponseDisplay/__snapshots__/SubmissionFiles.test.jsx.snap

+107-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ exports[`SubmissionFiles component snapshot files does not exist 1`] = `
1414
</Card>
1515
`;
1616

17-
exports[`SubmissionFiles component snapshot files exited for props 1`] = `
17+
exports[`SubmissionFiles component snapshot files existed for props 1`] = `
1818
<Card
1919
className="submission-files"
2020
>
@@ -75,11 +75,13 @@ exports[`SubmissionFiles component snapshot files exited for props 1`] = `
7575
"description": "description for the file",
7676
"downloadURL": "/valid-url-wink-wink",
7777
"name": "some file name.jpg",
78+
"size": 0,
7879
},
7980
Object {
8081
"description": "description for this file",
8182
"downloadURL": "/url-2",
8283
"name": "file number 2.jpg",
84+
"size": 0,
8385
},
8486
]
8587
}
@@ -100,15 +102,119 @@ exports[`SubmissionFiles component snapshot files exited for props 1`] = `
100102
"description": "description for the file",
101103
"downloadURL": "/valid-url-wink-wink",
102104
"name": "some file name.jpg",
105+
"size": 0,
103106
},
104107
Object {
105108
"description": "description for this file",
106109
"downloadURL": "/url-2",
107110
"name": "file number 2.jpg",
111+
"size": 0,
108112
},
109113
]
110114
}
111115
/>
112116
</Card.Footer>
113117
</Card>
114118
`;
119+
120+
exports[`SubmissionFiles component snapshot files size exceed 1`] = `
121+
<Card
122+
className="submission-files"
123+
>
124+
<Collapsible.Advanced
125+
defaultOpen={true}
126+
>
127+
<Collapsible.Trigger
128+
className="submission-files-title"
129+
>
130+
<h3>
131+
Submission Files (2)
132+
</h3>
133+
<Collapsible.Visible
134+
whenClosed={true}
135+
>
136+
<Icon
137+
src={[MockFunction icons.ArrowDropDown]}
138+
/>
139+
</Collapsible.Visible>
140+
<Collapsible.Visible
141+
whenOpen={true}
142+
>
143+
<Icon
144+
src={[MockFunction icons.ArrowDropUp]}
145+
/>
146+
</Collapsible.Visible>
147+
</Collapsible.Trigger>
148+
<Collapsible.Body
149+
className="submission-files-body"
150+
>
151+
<div
152+
className="submission-files-table"
153+
>
154+
<DataTable
155+
columns={
156+
Array [
157+
Object {
158+
"Cell": [MockFunction FileNameCell],
159+
"Header": "Name",
160+
"accessor": "name",
161+
},
162+
Object {
163+
"Cell": [MockFunction FileExtensionCell],
164+
"Header": "File Extension",
165+
"accessor": "name",
166+
"id": "extension",
167+
},
168+
Object {
169+
"Cell": [MockFunction FilePopoverCell],
170+
"Header": "File Metadata",
171+
"accessor": "",
172+
},
173+
]
174+
}
175+
data={
176+
Array [
177+
Object {
178+
"description": "description for the file",
179+
"downloadURL": "/valid-url-wink-wink",
180+
"name": "some file name.jpg",
181+
"size": 1610612737,
182+
},
183+
Object {
184+
"description": "description for this file",
185+
"downloadURL": "/url-2",
186+
"name": "file number 2.jpg",
187+
"size": 1610612737,
188+
},
189+
]
190+
}
191+
itemCount={2}
192+
>
193+
<DataTable.Table />
194+
</DataTable>
195+
</div>
196+
</Collapsible.Body>
197+
</Collapsible.Advanced>
198+
<Card.Footer
199+
className="text-right"
200+
>
201+
<div>
202+
<Icon
203+
className="d-inline-block align-middle"
204+
/>
205+
<span
206+
className="exceed-download-text"
207+
>
208+
209+
Exceeded the allow download size
210+
211+
</span>
212+
<Button
213+
disabled={true}
214+
>
215+
Download files
216+
</Button>
217+
</div>
218+
</Card.Footer>
219+
</Card>
220+
`;

src/containers/ResponseDisplay/messages.js

+10
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ const messages = defineMessages({
3636
defaultMessage: 'Retry download',
3737
description: 'Download files failed state label',
3838
},
39+
submissionFiles: {
40+
id: 'ora-grading.ResponseDisplay.SubmissionFiles.submissionFile',
41+
defaultMessage: 'Submission Files',
42+
description: 'Total submission files',
43+
},
44+
exceedFileSize: {
45+
id: 'ora-grading.ResponseDisplay.SubmissionFiles.fileSizeExceed',
46+
defaultMessage: 'Exceeded the allow download size',
47+
description: 'Exceed the allow download size error message',
48+
},
3949
});
4050

4151
export default messages;

src/data/constants/files.js

+3
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@ export const FileTypes = StrictDict({
1414
svg: 'svg',
1515
});
1616

17+
export const downloadSingleLimit = 1610612736; // 1.5GB
18+
export const downloadAllLimit = 10737418240; // 10GB
19+
1720
export default FileTypes;

0 commit comments

Comments
 (0)