Skip to content

Commit

Permalink
feat: (#417)
Browse files Browse the repository at this point in the history
-changes alert and banner display for failed uploads
-adds failed uploads list to the the list of files below the file drop area
  • Loading branch information
emi-hi authored Nov 27, 2024
1 parent 7a6fda7 commit 90aa0ad
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 40 deletions.
2 changes: 1 addition & 1 deletion django/api/services/spreadsheet_uploader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/app/components/AlertDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -33,7 +32,7 @@ const AlertDialog = (props) => {
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
<ErrorOutlineIcon className="error" /> {title}
<WarningAmberIcon className="error" /> {title}
</DialogTitle>
<DialogContent>
<DialogContent id="alert-dialog-description">
Expand Down
35 changes: 26 additions & 9 deletions frontend/src/uploads/UploadContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const UploadContainer = () => {
cancelAction: () => {},
cancelText: "cancel",
});
const [failedFiles, setFailedFiles] = useState([])

const axios = useAxios();
const axiosDefault = useAxios(true);
Expand Down Expand Up @@ -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] = {};
}
Expand Down Expand Up @@ -138,6 +141,12 @@ const UploadContainer = () => {

return { groupedCriticalErrors, groupedErrors, groupedWarnings, totalIssueCount };
};
const clearErrors = () => {
setGroupedCriticalErrors({})
setGroupedErrors({})
setGroupedWarnings({})
setTotalIssueCount({})
}

const showError = (error) => {
const { response: errorResponse } = error;
Expand Down Expand Up @@ -183,7 +192,6 @@ const UploadContainer = () => {
const errorCheck = responses.some(
(response) => !response.data.success
);

setAlertSeverity(errorCheck ? "error" : "success");
const message = responses
.map(
Expand All @@ -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;
Expand All @@ -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 && (
<div>
<span className="error">
{totalIssueCount.criticalErrors} Critical Errors
</span>
- Must fix before file can be processed
{groupedCriticalErrors && groupedCriticalErrors.Spreadsheet &&
groupedCriticalErrors.Spreadsheet['Missing Worksheet'] &&
<div>
File Upload Failed - The sheet name doesn't match the required
{groupedCriticalErrors.Spreadsheet['Missing Worksheet'].Rows[0]}”.<br/>

</div>
}
{groupedCriticalErrors && groupedCriticalErrors.Headers &&
groupedCriticalErrors.Headers['Missing Headers'] &&
<div>
The file is missing one or more required columns.
</div>
}
</div>
)}
{totalIssueCount.errors >= 1 && (
Expand Down Expand Up @@ -339,7 +355,6 @@ const UploadContainer = () => {
))}
</Alert>
) : null;

return (
<div className="row">
<div className="col-12 mr-2">
Expand Down Expand Up @@ -381,6 +396,8 @@ const UploadContainer = () => {
setAlert={setAlert}
loading={loading}
totalIssueCount={totalIssueCount}
clearErrors={clearErrors}
failedFiles={failedFiles}
/>
</Paper>
{adminUser && (
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/uploads/components/FileDrop.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
41 changes: 27 additions & 14 deletions frontend/src/uploads/components/FileDropArea.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,42 @@ 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);
uploadFiles.splice(found, 1);
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 (
<Grid container alignItems="center" key={name}>
<Grid item xs={7} className="upload-row">
<Grid item xs={7} className={uploadRowClassname}>
{name}
</Grid>
<Grid item xs={3} className="upload-row">
<Grid item xs={3} className={uploadRowClassname}>
{getFileSize(size)}
</Grid>
<Grid item xs={2} className="upload-row">
<Grid item xs={2} className={uploadRowClassname}>
{success == true &&
<Button
className="delete"
onClick={() => {
removeFile(file);
}}
type="button"
id="trash-button"
className="delete"
onClick={() => {
removeFile(file)
clearErrors();
}}
type="button"
id="trash-button"
>
<ClearIcon padding={0} sx={{ color: "red" }} />
</Button>
}
{success == false &&
<>Failed Upload</>
}
</Grid>
</Grid>
);
Expand All @@ -57,12 +64,13 @@ const FileDropArea = (props) => {
disabled={disabled}
setAlert={setAlert}
setFiles={setUploadFiles}
clearErrors={clearErrors}
/>
</span>
</Tooltip>
</Box>
</div>
{uploadFiles.length > 0 && (
{(uploadFiles.length > 0 || failedFiles.length > 0) && (
<Box className="upload-list" pt={3} rb={2}>
<Grid container direction="row">
<Grid item xs={7}>
Expand All @@ -72,7 +80,12 @@ const FileDropArea = (props) => {
<h3>Size</h3>
</Grid>
<Grid item xs={2} />
{uploadFiles.map((file) => FormRow(file))}
{failedFiles.map((failed, index) => {
return failed.map((file) => {
return FormRow(file, false);
});
})}
{uploadFiles.map((file) =>FormRow(file, true))}
</Grid>
</Box>
)}
Expand All @@ -86,4 +99,4 @@ FileDropArea.propTypes = {
uploadFiles: PropTypes.arrayOf(PropTypes.shape()).isRequired,
setAlert: PropTypes.func.isRequired,
};
export default FileDropArea;
export default FileDropArea;
60 changes: 49 additions & 11 deletions frontend/src/uploads/components/UploadIssues.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Box ml={4} mt={2}>
{errorMsg}
</Box>
)
}


const errorHeading = () => {
const missingHeadersError = groupedCriticalErrors?.Headers?.['Missing Headers']?.ExpectedType;
const missingWorksheetError = groupedCriticalErrors?.Spreadsheet?.['Missing Worksheet']?.ExpectedType;
return missingHeadersError || missingWorksheetError;
}


return (
<>
Expand All @@ -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'}
</h2>
<Box sx={{ ml: "2rem", mb: "1rem" }}>
{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'}
</Box>
{totalIssueCount.criticalErrors >= 1 && (
<Box ml={2} mt={2}>
<span className="error">
<strong>{totalIssueCount.criticalErrors} Critical Errors &nbsp;</strong>
</span>
- {criticalMsg}
</Box>
renderUploadFailed()
)}
{totalIssueCount.criticalErrors == 0 &&
<Box sx={{ ml: "2rem", mb: "1rem" }}>
Your file has been processed and contains the following errors and warnings. Please review them below
</Box>
}
{totalIssueCount.errors >= 1 && (
<Box ml={2} mt={2}>
<span className="error">
Expand All @@ -65,6 +98,8 @@ const UploadIssues = ({
- {warningMsg}
</Box>
)}
{totalIssueCount.criticalErrors == 0 && (
<>
<Accordion elevation={0} sx={{ "&:before": { height: "0px" } }}>
<AccordionSummary
aria-controls="panel1-content"
Expand Down Expand Up @@ -107,7 +142,10 @@ const UploadIssues = ({
)}
</AccordionDetails>
</Accordion>
{totalIssueCount.warnings >= 1 && totalIssueCount.errors === 0 && (
</>
)}
{
totalIssueCount.warnings >= 1 && totalIssueCount.errors === 0 && (
<Box>
<h3>Do you want to upload the file regardless of the warnings?</h3>
<Box mt={3}>
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/uploads/components/UploadPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ const UploadPage = (props) => {
downloadSpreadsheet,
setAlert,
loading,
totalIssueCount
totalIssueCount,
clearErrors,
failedFiles
} = props;

const selectionList = datasetList.map((obj, index) => (
<MenuItem key={index} value={obj.name}>
{obj.name}
Expand Down Expand Up @@ -99,6 +102,9 @@ const UploadPage = (props) => {
setAlert={setAlert}
setUploadFiles={setUploadFiles}
uploadFiles={uploadFiles}
totalIssueCount={totalIssueCount}
clearErrors={clearErrors}
failedFiles={failedFiles}
/>
</div>
<Box pt={3} rb={2}>
Expand Down

0 comments on commit 90aa0ad

Please sign in to comment.