diff --git a/django/api/services/spreadsheet_uploader.py b/django/api/services/spreadsheet_uploader.py index e6dd2285..c76059e5 100644 --- a/django/api/services/spreadsheet_uploader.py +++ b/django/api/services/spreadsheet_uploader.py @@ -55,7 +55,7 @@ def transform_data( if (missing_columns): errors_and_warnings['Headers'] = {} errors_and_warnings['Headers']['Missing Headers'] = { - "Expected Type": "The spreadsheet provided is missing headers", + "Expected Type": "missing one or more required columns", "Rows": missing_columns, "Severity": "Critical" } diff --git a/frontend/src/app/components/AlertDialog.js b/frontend/src/app/components/AlertDialog.js index 8a2b4cb2..c592bd27 100644 --- a/frontend/src/app/components/AlertDialog.js +++ b/frontend/src/app/components/AlertDialog.js @@ -6,8 +6,7 @@ import DialogActions from "@mui/material/DialogActions"; import DialogContent from "@mui/material/DialogContent"; import DialogContentText from "@mui/material/DialogContentText"; import DialogTitle from "@mui/material/DialogTitle"; -import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; - +import WarningAmberIcon from '@mui/icons-material/WarningAmber'; const AlertDialog = (props) => { const { open, @@ -33,7 +32,7 @@ const AlertDialog = (props) => { aria-describedby="alert-dialog-description" > - {title} + {title} diff --git a/frontend/src/uploads/UploadContainer.js b/frontend/src/uploads/UploadContainer.js index 1f8277d0..c5e76419 100644 --- a/frontend/src/uploads/UploadContainer.js +++ b/frontend/src/uploads/UploadContainer.js @@ -36,6 +36,7 @@ const UploadContainer = () => { cancelAction: () => {}, cancelText: "cancel", }); + const [failedFiles, setFailedFiles] = useState([]) const axios = useAxios(); const axiosDefault = useAxios(true); @@ -84,6 +85,8 @@ const UploadContainer = () => { const rows = errorDetails[errorType].Rows; const rowCount = rows.length; totalIssueCount.criticalErrors += rowCount; + setFailedFiles([...failedFiles, uploadFiles]) + setUploadFiles([]) if (!groupedCriticalErrors[column]) { groupedCriticalErrors[column] = {}; } @@ -138,6 +141,12 @@ const UploadContainer = () => { return { groupedCriticalErrors, groupedErrors, groupedWarnings, totalIssueCount }; }; + const clearErrors = () => { + setGroupedCriticalErrors({}) + setGroupedErrors({}) + setGroupedWarnings({}) + setTotalIssueCount({}) + } const showError = (error) => { const { response: errorResponse } = error; @@ -183,7 +192,6 @@ const UploadContainer = () => { const errorCheck = responses.some( (response) => !response.data.success ); - setAlertSeverity(errorCheck ? "error" : "success"); const message = responses .map( @@ -193,7 +201,6 @@ const UploadContainer = () => { .join("\n"); setAlert(true); setAlertContent(message); - const warnings = {}; responses.forEach((response, index) => { const responseWarnings = response.data.errors_and_warnings; @@ -210,18 +217,27 @@ const UploadContainer = () => { setGroupedErrors(groupedErrors); setGroupedWarnings(groupedWarnings); setTotalIssueCount(totalIssueCount); - setAlertDialogText({ title: - totalIssueCount.criticalErrors > 0 ? "Your upload contained critical errors that must be fixed before it can be processed!" : "Your file has been processed and contains the following errors and warnings!", + totalIssueCount.criticalErrors > 0 ? "File upload failed" : "Your file has been processed and contains the following errors and warnings!", content: ( <> {totalIssueCount.criticalErrors >= 1 && (
- - {totalIssueCount.criticalErrors} Critical Errors - - - Must fix before file can be processed + {groupedCriticalErrors && groupedCriticalErrors.Spreadsheet && + groupedCriticalErrors.Spreadsheet['Missing Worksheet'] && +
+ File Upload Failed - The sheet name doesn't match the required + “{groupedCriticalErrors.Spreadsheet['Missing Worksheet'].Rows[0]}”.
+ +
+ } + {groupedCriticalErrors && groupedCriticalErrors.Headers && + groupedCriticalErrors.Headers['Missing Headers'] && +
+ The file is missing one or more required columns. +
+ }
)} {totalIssueCount.errors >= 1 && ( @@ -339,7 +355,6 @@ const UploadContainer = () => { ))} ) : null; - return (
@@ -381,6 +396,8 @@ const UploadContainer = () => { setAlert={setAlert} loading={loading} totalIssueCount={totalIssueCount} + clearErrors={clearErrors} + failedFiles={failedFiles} /> {adminUser && ( diff --git a/frontend/src/uploads/components/FileDrop.js b/frontend/src/uploads/components/FileDrop.js index d0e4d71c..063a3548 100644 --- a/frontend/src/uploads/components/FileDrop.js +++ b/frontend/src/uploads/components/FileDrop.js @@ -5,12 +5,13 @@ import UploadIcon from "@mui/icons-material/Upload"; import { useDropzone } from "react-dropzone"; const FileDrop = (props) => { - const { disabled, setFiles, setAlert } = props; + const { disabled, setFiles, setAlert, clearErrors} = props; const [dropMessage, setDropMessage] = useState(""); const onDrop = useCallback((files) => { setAlert(false); setDropMessage(""); setFiles(files); + clearErrors(); }, []); const { getRootProps, getInputProps } = useDropzone({ onDrop }); const uploadBoxClassNames = disabled ? "file-upload disabled" : "file-upload"; diff --git a/frontend/src/uploads/components/FileDropArea.js b/frontend/src/uploads/components/FileDropArea.js index 69ef484e..eb59dbb5 100644 --- a/frontend/src/uploads/components/FileDropArea.js +++ b/frontend/src/uploads/components/FileDropArea.js @@ -6,7 +6,7 @@ import FileDrop from "./FileDrop"; import getFileSize from "../../app/utilities/getFileSize"; const FileDropArea = (props) => { - const { disabled, setUploadFiles, uploadFiles, setAlert } = props; + const { disabled, setUploadFiles, uploadFiles, setAlert, totalIssueCount, clearErrors, failedFiles } = props; const removeFile = (removedFile) => { const found = uploadFiles.findIndex((file) => file === removedFile); @@ -14,27 +14,34 @@ const FileDropArea = (props) => { setUploadFiles([...uploadFiles]); }; - function FormRow(file) { + function FormRow(file, success) { const { name, size } = file; + const uploadRowClassname = totalIssueCount.criticalErrors >= 1? 'error': success==false? 'error': 'upload-row' return ( - + {name} - + {getFileSize(size)} - + + {success == true && + } + {success == false && + <>Failed Upload + } ); @@ -57,12 +64,13 @@ const FileDropArea = (props) => { disabled={disabled} setAlert={setAlert} setFiles={setUploadFiles} + clearErrors={clearErrors} />
- {uploadFiles.length > 0 && ( + {(uploadFiles.length > 0 || failedFiles.length > 0) && ( @@ -72,7 +80,12 @@ const FileDropArea = (props) => {

Size

- {uploadFiles.map((file) => FormRow(file))} + {failedFiles.map((failed, index) => { + return failed.map((file) => { + return FormRow(file, false); + }); + })} + {uploadFiles.map((file) =>FormRow(file, true))}
)} @@ -86,4 +99,4 @@ FileDropArea.propTypes = { uploadFiles: PropTypes.arrayOf(PropTypes.shape()).isRequired, setAlert: PropTypes.func.isRequired, }; -export default FileDropArea; +export default FileDropArea; \ No newline at end of file diff --git a/frontend/src/uploads/components/UploadIssues.js b/frontend/src/uploads/components/UploadIssues.js index 3f4bebec..9b0e2080 100644 --- a/frontend/src/uploads/components/UploadIssues.js +++ b/frontend/src/uploads/components/UploadIssues.js @@ -27,6 +27,42 @@ const UploadIssues = ({ const criticalMsg = "Must fix before file can be processed"; const errorMsg = "Must fix before uploading"; const warningMsg = "Can upload without fixing"; + const renderUploadFailed = () => { + const missingHeadersError = groupedCriticalErrors?.Headers?.["Missing Headers"]; + let missingHeadersMsg = ''; + if (missingHeadersError) { + const missingColumns = missingHeadersError.Rows; + const columnsText = missingColumns.length === 1 + ? `column "${missingColumns[0]} is "` + : `columns "${missingColumns.join(', ')}" are `; + + missingHeadersMsg = `Your file has been processed and the ${columnsText} not found in the dataset. + Please ensure that your dataset matches the provided template, and that all required columns are present. + You can download the template for reference. Once corrected, you can upload your file again.`; + } + const missingWorksheetError = groupedCriticalErrors?.Spreadsheet?.["Missing Worksheet"]; + let missingWorksheetMsg = ''; + if (missingWorksheetError) { + const sheetName = groupedCriticalErrors.Spreadsheet['Missing Worksheet'].Rows[0] + missingWorksheetMsg = missingWorksheetError ? + `File Upload Failed - The sheet name doesn't match the required “${sheetName}”. + Please rename the sheet to the required “${sheetName}” before the next upload.` : ''; + } + const errorMsg = missingHeadersMsg || missingWorksheetMsg; + return ( + + {errorMsg} + + ) + } + + + const errorHeading = () => { + const missingHeadersError = groupedCriticalErrors?.Headers?.['Missing Headers']?.ExpectedType; + const missingWorksheetError = groupedCriticalErrors?.Spreadsheet?.['Missing Worksheet']?.ExpectedType; + return missingHeadersError || missingWorksheetError; + } + return ( <> @@ -36,19 +72,16 @@ const UploadIssues = ({ className="error" sx={{ marginLeft: 1, marginRight: 1 }} /> - Your file upload results + {totalIssueCount.criticalErrors >= 1 ? `File upload failed - ${errorHeading()}`: 'Your file upload results'} - - {totalIssueCount.criticalErrors > 0 ? 'Your file cannot be processed because it contains critical errors. Please review them below.': 'Your file has been processed and contains the following errors and warnings. Please review them below'} - {totalIssueCount.criticalErrors >= 1 && ( - - - {totalIssueCount.criticalErrors} Critical Errors   - - - {criticalMsg} - + renderUploadFailed() )} + {totalIssueCount.criticalErrors == 0 && + + Your file has been processed and contains the following errors and warnings. Please review them below + + } {totalIssueCount.errors >= 1 && ( @@ -65,6 +98,8 @@ const UploadIssues = ({ - {warningMsg} )} + {totalIssueCount.criticalErrors == 0 && ( + <> - {totalIssueCount.warnings >= 1 && totalIssueCount.errors === 0 && ( + + )} + { + totalIssueCount.warnings >= 1 && totalIssueCount.errors === 0 && (

Do you want to upload the file regardless of the warnings?

diff --git a/frontend/src/uploads/components/UploadPage.js b/frontend/src/uploads/components/UploadPage.js index fc1b1bca..4db57c7f 100644 --- a/frontend/src/uploads/components/UploadPage.js +++ b/frontend/src/uploads/components/UploadPage.js @@ -30,8 +30,11 @@ const UploadPage = (props) => { downloadSpreadsheet, setAlert, loading, - totalIssueCount + totalIssueCount, + clearErrors, + failedFiles } = props; + const selectionList = datasetList.map((obj, index) => ( {obj.name} @@ -99,6 +102,9 @@ const UploadPage = (props) => { setAlert={setAlert} setUploadFiles={setUploadFiles} uploadFiles={uploadFiles} + totalIssueCount={totalIssueCount} + clearErrors={clearErrors} + failedFiles={failedFiles} />