Skip to content

Commit

Permalink
feat: Add check to see if assets pre-exist when uploading
Browse files Browse the repository at this point in the history
  • Loading branch information
mavidser committed Oct 15, 2023
1 parent e5a3ee7 commit b961d0f
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 3 deletions.
3 changes: 2 additions & 1 deletion src/components/AssetsDropZone/container.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { connect } from 'react-redux';

import AssetsDropZone from '.';
import {
uploadAssets, uploadExceedMaxSize, uploadExceedMaxCount, uploadInvalidFileType,
preUploadCheck, uploadAssets, uploadExceedMaxSize, uploadExceedMaxCount, uploadInvalidFileType,
} from '../../data/actions/assets';

const mapStateToProps = state => ({
courseDetails: state.studioDetails.course,
});

const mapDispatchToProps = dispatch => ({
preUploadCheck: (files, courseDetails) => dispatch(preUploadCheck(files, courseDetails)),
uploadAssets: (files, courseDetails) => dispatch(uploadAssets(files, courseDetails)),
uploadExceedMaxCount: maxFileCount => dispatch(uploadExceedMaxCount(maxFileCount)),
uploadExceedMaxSize: maxFileSizeMB => dispatch(uploadExceedMaxSize(maxFileSizeMB)),
Expand Down
4 changes: 2 additions & 2 deletions src/components/AssetsDropZone/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default class AssetsDropZone extends React.Component {
this.props.uploadInvalidFileType();
}
} else {
this.props.uploadAssets(acceptedFiles, this.props.courseDetails);
this.props.preUploadCheck(acceptedFiles, this.props.courseDetails);
}
};

Expand Down Expand Up @@ -137,7 +137,7 @@ AssetsDropZone.propTypes = {
uploadExceedMaxCount: PropTypes.func.isRequired,
uploadExceedMaxSize: PropTypes.func.isRequired,
uploadInvalidFileType: PropTypes.func.isRequired,

preUploadCheck: PropTypes.func.isRequired,
};

AssetsDropZone.defaultProps = {
Expand Down
5 changes: 5 additions & 0 deletions src/components/AssetsPage/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import WrappedAssetsSearch from '../AssetsSearch/container';
import WrappedAssetsStatusAlert from '../AssetsStatusAlert/container';
import WrappedAssetsResultsCount from '../AssetsResultsCount/container';
import WrappedAssetsClearFiltersButton from '../AssetsClearFiltersButton/container';
import WrappedAssetsUploadConfirm from '../AssetsUploadConfirm/container';
import WrappedMessage from '../../utils/i18n/formattedMessageWrapper';
import messages from './displayMessages';
import styles from './AssetsPage.scss';
Expand Down Expand Up @@ -197,6 +198,10 @@ export default class AssetsPage extends React.Component {
</div>
<div className="container">
<div className="row">
<div className="col-12">
<WrappedAssetsUploadConfirm
/>
</div>
<div className="col-12">
<WrappedAssetsStatusAlert
statusAlertRef={(input) => { this.statusAlertRef = input; }}
Expand Down
5 changes: 5 additions & 0 deletions src/components/AssetsUploadConfirm/AssetsUploadConfirm.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import 'edx-bootstrap';

.assets-upload-confirm-spacer {
margin-bottom: 10px;
}
21 changes: 21 additions & 0 deletions src/components/AssetsUploadConfirm/container.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { connect } from 'react-redux';

import { uploadAssets } from '../../data/actions/assets';
import AssetsUploadConfirm from '.';

const mapStateToProps = state => ({
files: state.metadata.files,
pre_upload_error: state.metadata.pre_upload_error,
courseDetails: state.studioDetails.course,
});

const mapDispatchToProps = dispatch => ({
uploadAssets: (assets, courseDetails) => dispatch(uploadAssets(assets, courseDetails)),
});

const WrappedAssetsUploadConfirm = connect(
mapStateToProps,
mapDispatchToProps,
)(AssetsUploadConfirm);

export default WrappedAssetsUploadConfirm;
16 changes: 16 additions & 0 deletions src/components/AssetsUploadConfirm/displayMessages.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { defineMessages } from 'react-intl';

const messages = defineMessages({
assetsUploadConfirmMessage: {
id: 'assetsUploadConfirmMessage',
defaultMessage: '0 files uploaded. {pre_upload_error}. Continuing to upload will overwrite them. If this is not what you want, retry uploading the files with different names.',
description: 'The message displayed in the confirmation box shown when uploading files with pre-existing names',
},
assetUploadConfirmOverwrite: {
id: 'assetUploadConfirmOverwrite',
defaultMessage: 'Overwrite',
description: 'The message displayed in the button to confirm overwriting the files',
}
});

export default messages;
90 changes: 90 additions & 0 deletions src/components/AssetsUploadConfirm/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, StatusAlert } from '@edx/paragon';
import { FormattedNumber } from 'react-intl';

import { assetActions } from '../../data/constants/actionTypes';
import WrappedMessage from '../../utils/i18n/formattedMessageWrapper';
import { ASSET_STATUS_SHAPE } from '../../utils/constants';
import styles from './AssetsUploadConfirm.scss';
import messages from './displayMessages';
import { courseDetails } from '../../utils/testConstants';

const defaultState = {
statusAlertOpen: false,
};

export default class AssetsUploadConfirm extends React.Component {
constructor(props) {
super(props);
this.state = defaultState;
}

componentWillReceiveProps(nextProps) {
const { files, courseDetails, pre_upload_error } = nextProps;
this.updateStates(files, courseDetails, pre_upload_error);
}

updateStates = (files, courseDetails, pre_upload_error) => {
this.setState({
statusAlertOpen: pre_upload_error ? true : false,
})
};

uploadFiles = () => {
this.props.uploadAssets(this.props.files, this.props.courseDetails)
}

onClose = () => {
this.setState(defaultState);
}

render() {
const uploadFiles = this.uploadFiles;
const { statusAlertOpen } = this.state
const { pre_upload_error } = this.props
content = (
<div>
<WrappedMessage
message={messages.assetsUploadConfirmMessage}
values={{ pre_upload_error }}
/>
<br className={styles['assets-upload-confirm-spacer']}/>
<Button
buttonType="primary"
label={<WrappedMessage message={messages.assetUploadConfirmOverwrite}/>}
onClick={uploadFiles}
/>
</div>
)

return (
<StatusAlert
alertType="danger"
dialog={content}
open={statusAlertOpen}
onClose={this.onClose}
/>
);
}
}

AssetsUploadConfirm.propTypes = {
files: PropTypes.arrayOf(PropTypes.string),
uploadAssets: PropTypes.func.isRequired,
courseDetails: PropTypes.shape({
lang: PropTypes.string,
url_name: PropTypes.string,
name: PropTypes.string,
display_course_number: PropTypes.string,
num: PropTypes.string,
org: PropTypes.string,
id: PropTypes.string,
revision: PropTypes.string,
}),
pre_upload_error: PropTypes.string,
};

AssetsUploadConfirm.defaultProps = {
files: [],
};
27 changes: 27 additions & 0 deletions src/data/actions/assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ export const uploadAssetFailure = (asset, response) => ({
});

export const uploadAssets = (assets, courseDetails) => (dispatch) => {
dispatch(setPreUploadError(''))
dispatch(setFilesToUpload([]));
dispatch(uploadingAssets(assets.length));
// gather all the promises into a single promise that can be returned
return Promise.all(assets.map(asset => (
Expand All @@ -291,6 +293,31 @@ export const uploadAssets = (assets, courseDetails) => (dispatch) => {
)));
};

export const preUploadCheck = (assets, courseDetails) => (dispatch) => {
clientApi.preUploadCheck(courseDetails.id, assets.map(asset => asset.name))
.then((response) => {
if (response.ok) {
return response.json().then((json) => {
return dispatch(uploadAssets(assets, courseDetails));
});
}
return response.json().then((json) => {
dispatch(setFilesToUpload(assets));
return dispatch(setPreUploadError(json.reason));
});
})
};

export const setFilesToUpload = (files) => ({
type: assetActions.files.FILES_UPDATE,
files,
});

export const setPreUploadError = (pre_upload_error) => ({
type: assetActions.files.FILES_PRE_UPLOAD_ERROR,
pre_upload_error,
});

export const uploadExceedMaxCount = maxFileCount => ({
type: assetActions.upload.UPLOAD_EXCEED_MAX_COUNT_ERROR,
maxFileCount,
Expand Down
12 changes: 12 additions & 0 deletions src/data/api/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ export function requestToggleLockAsset(courseId, asset) {
});
}

export function preUploadCheck(courseId, filenames) {
return fetch(`${endpoints.assets}/${courseId}/pre_upload_check`, {
credentials: 'same-origin',
method: 'post',
body: JSON.stringify({ filenames }),
headers: {
'Accept': 'application/json',
'X-CSRFToken': Cookies.get('csrftoken'),
},
});
}

export function postUploadAsset(courseId, file) {
const data = new FormData();
data.append('file', file);
Expand Down
4 changes: 4 additions & 0 deletions src/data/constants/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export const assetActions = {
imagePreview: {
IMAGE_PREVIEW_UPDATE: 'IMAGE_PREVIEW_UPDATE',
},
files: {
FILES_UPDATE: 'FILES_UPDATE',
FILES_PRE_UPLOAD_ERROR: 'FILES_PRE_UPLOAD_ERROR',
},
};

export const accessibilityActions = {
Expand Down
20 changes: 20 additions & 0 deletions src/data/reducers/assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,24 @@ export const search = (state = searchInitial, action) => {
}
};

export const files = (state = [], action) => {
switch(action.type) {
case assetActions.files.FILES_UPDATE:
return action.files;
default:
return state;
}
}

export const pre_upload_error = (state = '', action) => {
switch(action.type) {
case assetActions.files.FILES_PRE_UPLOAD_ERROR:
return action.pre_upload_error;
default:
return state;
}
}

export const status = (state = {}, action) => {
switch (action.type) {
case assetActions.request.REQUEST_ASSETS_SUCCESS:
Expand Down Expand Up @@ -285,4 +303,6 @@ export const metadata = combineReducers({
request,
sort,
status,
files,
pre_upload_error,
});

0 comments on commit b961d0f

Please sign in to comment.