From 74423bf359e074ae3c77b462a52c57dcea571b46 Mon Sep 17 00:00:00 2001 From: Leangseu Kim Date: Tue, 19 Apr 2022 14:51:04 -0400 Subject: [PATCH] feat: prevent download large files --- .../ResponseDisplay/SubmissionFiles.jsx | 28 ++++- .../ResponseDisplay/SubmissionFiles.test.jsx | 61 ++++++++-- .../SubmissionFiles.test.jsx.snap | 108 +++++++++++++++++- src/containers/ResponseDisplay/messages.js | 10 ++ src/data/constants/files.js | 3 + 5 files changed, 198 insertions(+), 12 deletions(-) diff --git a/src/containers/ResponseDisplay/SubmissionFiles.jsx b/src/containers/ResponseDisplay/SubmissionFiles.jsx index bdc0715..ebbaa43 100644 --- a/src/containers/ResponseDisplay/SubmissionFiles.jsx +++ b/src/containers/ResponseDisplay/SubmissionFiles.jsx @@ -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 { - + { + this.canDownload ? : ( +
+ + {intl.formatMessage(messages.exceedFileSize)} + +
+ ) + }
) : ( diff --git a/src/containers/ResponseDisplay/SubmissionFiles.test.jsx b/src/containers/ResponseDisplay/SubmissionFiles.test.jsx index 81fd7eb..3fcc1c9 100644 --- a/src/containers/ResponseDisplay/SubmissionFiles.test.jsx +++ b/src/containers/ResponseDisplay/SubmissionFiles.test.jsx @@ -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(); + beforeEach(() => { + el = shallow(); }); 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); + }); + }); }); }); }); diff --git a/src/containers/ResponseDisplay/__snapshots__/SubmissionFiles.test.jsx.snap b/src/containers/ResponseDisplay/__snapshots__/SubmissionFiles.test.jsx.snap index bb3b9cb..3fa24fd 100644 --- a/src/containers/ResponseDisplay/__snapshots__/SubmissionFiles.test.jsx.snap +++ b/src/containers/ResponseDisplay/__snapshots__/SubmissionFiles.test.jsx.snap @@ -14,7 +14,7 @@ exports[`SubmissionFiles component snapshot files does not exist 1`] = ` `; -exports[`SubmissionFiles component snapshot files exited for props 1`] = ` +exports[`SubmissionFiles component snapshot files existed for props 1`] = ` @@ -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`] = ` `; + +exports[`SubmissionFiles component snapshot files size exceed 1`] = ` + + + +

+ Submission Files (2) +

+ + + + + + +
+ +
+ + + +
+
+
+ +
+ + + + Exceeded the allow download size + + + +
+
+
+`; diff --git a/src/containers/ResponseDisplay/messages.js b/src/containers/ResponseDisplay/messages.js index bedc8d6..cf89d3a 100644 --- a/src/containers/ResponseDisplay/messages.js +++ b/src/containers/ResponseDisplay/messages.js @@ -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; diff --git a/src/data/constants/files.js b/src/data/constants/files.js index 7439a1c..46bc5a3 100644 --- a/src/data/constants/files.js +++ b/src/data/constants/files.js @@ -14,4 +14,7 @@ export const FileTypes = StrictDict({ svg: 'svg', }); +export const downloadSingleLimit = 1610612736; // 1.5GB +export const downloadAllLimit = 10737418240; // 10GB + export default FileTypes;