Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bulk partial process #1279

Merged
merged 6 commits into from
Dec 11, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
working through buk procesing error handling
BirdMachine committed Nov 20, 2023
commit 223c2b027b73d5a2cc0a91e95c2981f8ff5ac5d3
485 changes: 266 additions & 219 deletions src/src/components/ingest/bulk.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component } from "react";
import {Component} from "react";
import Paper from '@material-ui/core/Paper';
import Button from '@mui/material/Button';
import DownloadIcon from '@mui/icons-material/Download';
@@ -14,35 +14,39 @@ import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSpinner, faExclamationTriangle, faFileDownload } from "@fortawesome/free-solid-svg-icons";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {
faSpinner,faExclamationTriangle,faFileDownload
} from "@fortawesome/free-solid-svg-icons";
import DescriptionIcon from '@material-ui/icons/Description';
import LinearProgress from '@material-ui/core/LinearProgress';
import * as prettyBytes from 'pretty-bytes';
import _ from 'lodash';
import { parseErrorMessage, toTitleCase } from "../../utils/string_helper";
import { readString, jsonToCSV } from 'react-papaparse'
import {parseErrorMessage,toTitleCase} from "../../utils/string_helper";
import {readString} from 'react-papaparse';
import {CSVLink} from "react-csv";

import {ingest_api_bulk_entities_upload,
ingest_api_bulk_entities_register,
ingest_api_users_groups} from '../../service/ingest_api';
import { Box } from "@mui/system";
import { Link } from "@material-ui/core";
import {
ingest_api_bulk_entities_upload,
ingest_api_bulk_entities_register,
ingest_api_users_groups
} from '../../service/ingest_api';
import {Box} from "@mui/system";
import {Link} from "@material-ui/core";


class bulkCreation extends Component {

constructor(props) {
super(props);
this.state = {
alertStatus:"danger",
bulkLength:0,
activeStep:0,
loading:false,
steps:['Upload Your File', 'Review Validation', 'Register', 'Complete'],
currentDateTime: Date().toLocaleString(),
headers : [
alertStatus: "danger",
bulkLength: 0,
activeStep: 0,
loading: false,
steps: ['Upload Your File', 'Review Validation', 'Register', 'Complete'],
currentDateTime:Date().toLocaleString(),
headers: [
"Source Id",
"Lab Id",
"Sample Type",
@@ -51,24 +55,25 @@ class bulkCreation extends Component {
"Description",
"Rui Location"
],
error_message_detail: "",
response_status: "",
error_message: "",
error_status:false,
error_message_detail: "",
response_status: "",
error_status: false,
mixed_status: false,
error_message: "",
error_message_extended:"",
errorSet:[],
rowLimitClass:"",
success_status:false,
success_message:"",
validation:true,
validated:[],
showTable:false,
tsvFile:"",
registeredStatus:false,
uploadTimer:"",
uploadedSources:[],
finalTableReady:false,
complete: false
errorSet: [],
rowLimitClass: "",
success_status: false,
success_message: "",
validation: true,
validated: [],
showTable: false,
tsvFile: "",
registeredStatus: false,
uploadTimer: "",
uploadedSources: [],
finalTableReady: false,
complete: false
};


@@ -100,16 +105,14 @@ class bulkCreation extends Component {
g => g.uuid !== process.env.REACT_APP_READ_ONLY_GROUP_ID
);
// return groups;
this.setState({
groups: groups,
inputValue_group_uuid: groups[0].uuid
});
this.setState({groups: groups,
inputValue_group_uuid:groups[0].uuid});

} else if (results.status === 401) {
localStorage.setItem("isAuthenticated", false);
window.location.reload();
}else{
this.setState({ groups: ["NA"] });
this.setState({ groups:["NA"] });
}
});
}
@@ -161,27 +164,23 @@ class bulkCreation extends Component {
}
}
console.debug("errors",errors);
this.setState({
errorSet: errors
})
this.setState({errorSet:errors})
}



handleNext = () => {
console.debug("handleNext");
var newStep = this.state.activeStep +1
this.setState({
activeStep: newStep
})
this.setState({activeStep:newStep})
};

handleBack = () => {
console.debug("handleBack");
var newStep = this.state.activeStep -1
this.setState({
activeStep: newStep,
error_status:false,
activeStep: newStep,
error_status: false,
success_status:false
})
};
@@ -192,9 +191,7 @@ class bulkCreation extends Component {


handleGroupSelect = (evt) => {
this.setState({
group_uuid: evt.target.value
});
this.setState({group_uuid:evt.target.value});
}


@@ -209,10 +206,8 @@ class bulkCreation extends Component {

return (
<CSVLink
style={{
float: "right",
padding: "12px",
}}
style={{float: "right",
padding:"12px",}}
headers={headers}
data={this.state.uploadedSources}>
<Button
@@ -229,9 +224,7 @@ handleFileGrab = e => {
var newName = grabbedFile.name.replace(/ /g, '_')
var newFile = new File([grabbedFile], newName);
if (newFile && newFile.name.length > 0) {
this.setState({
tsvFile: newFile
}, () => {
this.setState({tsvFile:newFile}, () => {
this.handleNext();
});
}else{
@@ -240,15 +233,12 @@ handleFileGrab = e => {
};

handleUpload= () =>{
this.setState({
loading:true,
uploadTimer:"00:00"
});
this.setState({loading: true,
uploadTimer:"00:00"});
if(!this.state.group_uuid || this.state.group_uuid.length <=0){
this.setState({
// Grab the first one on in the select if they havent manually changed it
group_uuid:this.state.groups[0].uuid,
});
group_uuid:this.state.groups[0].uuid,});
}
if( localStorage.getItem("info") !== null ){
const formData = new FormData()
@@ -259,10 +249,10 @@ handleUpload= () =>{
}
if (resp.status === 201) {
this.setState({
success_status:true,
success_status: true,
success_message:this.props.bulkType+"Uploaded Successfully",
loading:false,
bulkFileID:resp.results.temp_id
loading: false,
bulkFileID: resp.results.temp_id
});
this.parseUpload(); // Table of file contents builds here
this.handleNext();
@@ -281,54 +271,46 @@ handleUpload= () =>{
console.debug("[[[[[[[[[[ parsedError",parsedError);
this.handleErrorCompiling(parsedError); // Error Array's set in that not here
this.setState({
error_status: true,
submit_error: true,
submitting: false
error_status:true,
submit_error:true,
submitting: false
});
console.debug("DEBUG",this.state.error_message_detail);
this.setState({
loading:false,
}, () => {
this.setState({loading:false,}, () => {
});
}
})
.catch((error) => {
console.debug("handleUpload ERROR", error);
this.setState({
submit_error:error,
error_status:true,
error_message_detail: parseErrorMessage(error),
error_message:"Error" });
submit_error: error,
error_status: true,
error_message_detail:parseErrorMessage(error),
error_message: "Error"
});
console.debug("SUBMIT error", error)
this.setState({
loading:false,
});
this.setState({loading:false,});
});
}else{
this.setState({
submit_error:"TokenExpired",
error_status:true,
submit_error: "TokenExpired",
error_status: true,
error_message_detail:"Please log in again",
error_message:"Token Expired" });
error_message: "Token Expired"
});
console.debug("SUBMIT error", "Login Expired")
this.setState({
loading:false,
});
this.setState({loading:false,});
}
}


handleRegister = () =>{
this.setState({
loading:true,
uploadTimer:"00:00"
});
this.setState({loading: true,
uploadTimer:"00:00"});
if( localStorage.getItem("info") !== null ){
console.debug("this.state.bulkFileID", this.state.bulkFileID);
var fileData ={
"temp_id":this.state.bulkFileID,
"group_uuid":this.state.group_uuid
}
var fileData ={"temp_id": this.state.bulkFileID,
"group_uuid":this.state.group_uuid}
console.debug("REG HERE");
ingest_api_bulk_entities_register(this.props.bulkType, fileData, JSON.parse(localStorage.getItem("info")).groups_token)
.then((resp) => {
@@ -339,111 +321,135 @@ handleRegister = () =>{
var respData = resp.results.data;
console.debug("respData",respData);
let respInfo = _.map(respData, (value, prop) => {
return { "prop": prop, "value": value };
return { "prop":prop, "value":value };
});

// Cant depend on first one if its a partial success
if( respInfo[1] && respInfo[1].value['error']){
this.setState({
submit_error:"error",
error_status:true,
success_message:null,
error_message_detail: "Errors were found. Please review &amp; and try again",
error_message:"Error" });
submit_error: "error",
error_status: true,
success_message: null,
error_message_detail:"Errors were found. Please review &amp; and try again",
error_message: "Error"
});
console.debug("SUBMIT error", "error")
this.setState({
loading:false,
});
this.setState({loading:false,});

}else{
console.debug("No RESP.results Detected");
var respToArray = Object.values(respData)
this.setState({
success_status:true,
alertStatus:"success",
success_message:this.props.bulkType+" Registered Successfully",
loading:false,
success_status: true,
alertStatus: "success",
success_message: this.props.bulkType+" Registered Successfully",
loading: false,
uploadedBulkFile:respInfo,
uploadedSources:respToArray,
complete: true,
uploadedSources: respToArray,
complete: true,
registeredStatus:true
}, () => {
});
}
}
} else if (resp.error.response && resp.error.response.status === 504) {
console.debug('%c⭗', 'color:#ff005d', "504 Error: ", resp.error);

} else if (resp.error.response && (resp.error.response.status === 504 || resp.error.response.status === 500)) {
console.debug('%c⭗', 'color:#ff005d', resp.error.response.status+" Error: ", resp.error);
console.debug('%c⭗', 'color:#ff005d', "Err Response", resp.error.response);
this.props.reportError(resp.error);
} else {
} else if (resp.status === 207) { // Partial Success
console.debug('%c⭗ 207 |', 'color:#FBFF00', resp.data.status);
// this.props.reportError(resp.error);
var mixedResponseArray = Object.values(respData)
this.setState({
mixed_status: true,
alertStatus: "mixed",
mixed_message: "Partial Regsitsration Success",
loading: false,
uploadedSources: mixedResponseArray,
complete: true,
registeredStatus:true
}, () => {
});
} else {
console.debug('%c⭗', 'color:#ff005d', "Error: ",resp );
// var respError = resp.err.response.data
var respError = resp.error
console.debug("=====ERROR=====, respError", respError, respError.status, respError.data);
this.parseRegErrorFrame(resp);
this.setState({
error_status: true,
submit_error: true,
submitting: false,
loading:false,
error_message_detail: parseErrorMessage(respError.status),
success_message:null,
response_status:resp.Error});
error_status: true,
submit_error: true,
submitting: false,
loading: false,
error_message_detail:parseErrorMessage(respError.status),
success_message: null,
response_status: resp.Error
});
console.debug("DEBUG",this.state.error_message_detail);
}
})
.catch((error) => {
console.debug("SUBMIT error", error);
console.debug("======= response ", error.response);
this.setState({
submit_error:error,
error_status:true,
error_message_detail: parseErrorMessage(error),
loading:false,
error_message:"Error" });
submit_error: error,
error_status: true,
error_message_detail:parseErrorMessage(error),
loading: false,
error_message: "Error"
});
});
}
}


parseRegErrorFrame = (errResp) => {
console.debug('%c⊙', 'color:#FF0000', "parseRegErrorFrame", errResp );
var parsedError;

if(errResp.results && errResp.results.data && errResp.results.data.data){
parsedError = parseErrorMessage(errResp.results.data.data);
}else if(errResp.results && errResp.results.data){
parsedError = parseErrorMessage(errResp.results.data);
}else if(errResp.results && errResp.results.data){
parsedError = parseErrorMessage(errResp.results.data);
}else{
var regErrorSet = errResp.err.response.data
var errRows = regErrorSet.data;
var errMessage = regErrorSet.status;
console.debug("errRows",errRows, "errMessage",errMessage);
parsedError=errResp;
let regErrorSet ={}
if(errResp.err && errResp.err.response.data){
regErrorSet = errResp.err.response.data
}else if(errResp.error && errResp.error.response.data){
regErrorSet = errResp.error.response.data
}
var errRows = regErrorSet.data;
var errMessage = regErrorSet.status;
console.debug("errRows",errRows, "errMessage",errMessage);
parsedError=errResp;
}

this.handleErrorCompiling(parsedError); // Error Array's set in that not here
this.setState({
error_status: true,
submit_error: true,
error_message:errMessage,
error_status: true,
submit_error: true,
error_message: errMessage,
success_message:null,
submitting: false,
submitting: false,
response_status:errResp.Error
});
console.debug("DEBUG",this.state.error_message_detail);
this.setState({
loading:false,
}, () => {
this.setState({loading:false,}, () => {
});
}


parseUpload = () =>{
var config = {
header: true,
skipEmptyLines: true,
complete: this.parseResults,
header: true,
skipEmptyLines:true,
complete: this.parseResults,
}
var parsedData = readString(this.state.tsvFile,config);
this.setState({
parsedData:parsedData
});
this.setState({parsedData:parsedData});
}
parseResults = (results) =>{
console.debug("results",results.data);
@@ -454,19 +460,17 @@ parseResults = (results) =>{
console.debug("rowCount",rowCount );
if(rowCount>40){
this.setState({
rowLimitClass:"rowLimitError",
submit_error:"Row Limit Exceeded",
loading:false,
error_status:true,
error_message_detail:"Row Limit Exceeded",
error_message:"40 row limit exceeded (total rows:"+rowCount+"). Please split your data across multiple files (est "+expectedSplit+" files)" ,
rowLimitClass: "rowLimitError",
submit_error: "Row Limit Exceeded",
loading: false,
error_status: true,
error_message_detail: "Row Limit Exceeded",
error_message: "40 row limit exceeded (total rows:"+rowCount+"). Please split your data across multiple files (est "+expectedSplit+" files)" ,
error_message_extended:"40 row limit exceeded (total rows:"+rowCount+"). Please split your data across multiple files (est "+expectedSplit+" files) "
});
}else{
this.setState({
uploadedSources:results.data,
finalTableReady:true
});
this.setState({uploadedSources:results.data,
finalTableReady:true});
}
}

@@ -513,9 +517,7 @@ getStepContent = (step) =>{


showUploadedStuff(){
this.setState({
showTable:true
})
this.setState({showTable:true})
}

renderStatusButon = (message) =>{
@@ -551,9 +553,7 @@ renderFileGrabber = () =>{
}

makeRegistered = () =>{
this.setState({
validation:true
})
this.setState({validation:true})
}

renderUploadSlide = () =>{
@@ -563,17 +563,17 @@ renderFileGrabber = () =>{
{this.state.error_status === false&&(
<div className="">
<div className="text-left">
<h4> <DescriptionIcon style={{ fontSize: 40 }} /> {this.state.tsvFile.name} <small><em>({prettyBytes(this.state.tsvFile.size)})</em></small></h4>
<h4> <DescriptionIcon style={{ fontSize:40 }} /> {this.state.tsvFile.name} <small><em>({prettyBytes(this.state.tsvFile.size)})</em></small></h4>
{this.renderGroupSelect()}
</div>
</div>
)}
{/* text */}
{this.state.error_status && !this.state.loading &&(
<div className="col-7">
{this.renderInvalidTable()}
</div>
)}
<div className="col-7">
{this.renderInvalidTable()}
</div>
)}


<div className="col-3">
@@ -582,7 +582,7 @@ renderFileGrabber = () =>{
<Button
onClick={() => this.handleUpload()}
className="btn-lg btn-block"
style={{ padding: "12px" }}
style={{ padding:"12px" }}
variant="contained"
color="primary" >
{this.renderStatusButon("Upload")}
@@ -592,7 +592,7 @@ renderFileGrabber = () =>{
<Button
onClick={() => this.handleBack()}
className="btn-lg btn-block"
style={{ padding: "12px" }}
style={{ padding:"12px" }}
variant="text"
color="primary" >
Replace
@@ -626,7 +626,7 @@ renderFileGrabber = () =>{
<Button
onClick={() => this.handleReset()}
className="btn-lg btn-block m-0"
style={{ padding: "12px", float:"right" }}
style={{ padding:"12px", float:"right" }}
variant="contained"
color="error" >
Restart
@@ -649,6 +649,11 @@ renderFileGrabber = () =>{
{this.renderInvalidTable()}
</div>
)}
{this.state.mixed_status && !this.state.loading &&(
<div>
{this.renderMixedSuccess()}
</div>
)}
{!this.state.error_status && (
<div>
{this.state.complete && (
@@ -666,7 +671,7 @@ renderFileGrabber = () =>{
<Button
onClick={() => this.handleRegister()}
className="btn-lg"
style={{ padding: "12px" }}
style={{ padding:"12px" }}
variant="contained"
color="primary" >
{this.renderStatusButon("Register")}
@@ -678,7 +683,7 @@ renderFileGrabber = () =>{
<Button
onClick={() => this.handleReset()}
className="btn-lg mt-2 align-self-end"
style={{ padding: "12px" }}
style={{ padding:"12px" }}
variant="contained"
color="primary" >
Close
@@ -689,7 +694,9 @@ renderFileGrabber = () =>{
{/* Errors */}

{this.state.error_message &&(
<div style={{marginBottom:"10px", width:"100%", display:"inline-block"}}>
<div style={{
marginBottom:"10px", width:"100%", display:"inline-block"
}}>

<Typography
variant="h6"
@@ -699,7 +706,9 @@ renderFileGrabber = () =>{

<Typography
variant="caption"
style={{marginBottom:"10px", width:"100%", display:"inline-block"}}
style={{
marginBottom:"10px", width:"100%", display:"inline-block"
}}
color="error">
{this.state.error_message_extended}
</Typography>
@@ -716,7 +725,7 @@ renderFileGrabber = () =>{
<Button
onClick={() => this.handleReset()}
className="btn-lg btn-block m-0"
style={{ padding: "12px", float:"right" }}
style={{ padding:"12px", float:"right" }}
variant="contained"
color="error" >
Restart
@@ -792,29 +801,33 @@ renderFileGrabber = () =>{
if(this.props.bulkType.toLowerCase() === "samples"){
headCells = [
// { id: 'source_id', label: 'Source Id ' },
{ id: 'lab_id', label: 'Lab Id ' },
{ id: 'sample_category', label: 'Type' },
{ id: 'organ_type', label: 'Organ ' },
{ id: 'sample_protocol', label: 'Protocol ' },
{ id: 'description', label: 'Description ' },
{ id:'lab_id', label:'Lab Id ' },
{ id:'sample_category', label:'Type' },
{ id:'organ_type', label:'Organ ' },
{ id:'sample_protocol', label:'Protocol ' },
{ id:'description', label:'Description ' },
];
}else if(this.props.bulkType.toLowerCase() === "donors"){
headCells = [
{ id: 'lab_id', label: 'Lab ID ' },
{ id: 'lab_name', label: 'Lab Name ' },
{ id: 'selection_protocol', label: 'Protocol ', width:"40%" },
{ id: 'description', label: 'Description ' },
{ id:'lab_id', label:'Lab ID ' },
{ id:'lab_name', label:'Lab Name ' },
{
id:'selection_protocol', label:'Protocol ', width:"40%"
},
{ id:'description', label:'Description ' },
];
}
if(this.state.registeredStatus === true){
headCells.unshift({ id: 'hubmap_id', disablePadding: true, label: 'Hubmap ID ', width:"" },)
headCells.unshift({
id:'hubmap_id', disablePadding:true, label:'Hubmap ID ', width:""
},)
}


return(
<TableContainer
component={Paper}
style={{ maxHeight: 450 }}
style={{ maxHeight:450 }}
>
<Table
aria-label={"Uploaded "+this.props.bulkType }
@@ -840,42 +853,78 @@ renderFileGrabber = () =>{
{this.renderTableBody()}
</Table>
</TableContainer>
)}
)
}




renderInvalidTable = () =>{
return(
<TableContainer
component={Paper}
style={{ maxHeight: 450 }}
>
<Table
aria-label={"Uploaded Errors"+this.props.bulkType }
size="small"
stickyHeader
className="table table-striped table-hover mb-0 uploadedTable ">
<TableHead className="thead-dark font-size-sm">
<TableRow >
<TableCell component="th" variant="head" width="7%">Row</TableCell>
<TableCell component="th" variant="head">Error</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.state.errorSet.map((item, index) => (
<TableRow key={("rowitem_"+index)} >
<TableCell className="" scope="row">
{item.row}
</TableCell>
<TableCell className="" scope="row">
{item.message}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<TableContainer
component={Paper}
style={{ maxHeight:450 }}
>
<Table
aria-label={"Uploaded Errors"+this.props.bulkType }
size="small"
stickyHeader
className="table table-striped table-hover mb-0 uploadedTable ">
<TableHead className="thead-dark font-size-sm">
<TableRow >
<TableCell component="th" variant="head" width="7%">Row</TableCell>
<TableCell component="th" variant="head">Error</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.state.errorSet.map((item, index) => (
<TableRow key={("rowitem_"+index)} >
<TableCell className="" scope="row">
{item.row}
</TableCell>
<TableCell className="" scope="row">
{item.message}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>

)
}

renderMixedSuccess = () =>{
return(
<TableContainer
component={Paper}
style={{ maxHeight:450 }}
>
<Table
aria-label={"Uploaded Errors"+this.props.bulkType }
size="small"
stickyHeader
className="table table-striped table-hover mb-0 uploadedTable ">
<TableHead className="thead-dark font-size-sm">
<TableRow >
<TableCell component="th" variant="head" width="7%">Row</TableCell>
<TableCell component="th" variant="head">Error</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.state.errorSet.map((item, index) => (
<TableRow key={("rowitem_"+index)} className={item.error ? "error" : "ssuccess"} >
<TableCell className="" scope="row">
{item.row}
</TableCell>
<TableCell className="" scope="row">
{item.error ? item.error : "Successfully Registered as "+item.hubmap_id}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>

)
}
@@ -952,10 +1001,8 @@ renderFileGrabber = () =>{
{this.state.error_message &&(
<div>
<Box
sx={{
textAlign: "left",
padding: "0.5rem",
}}>
sx={{textAlign:"left",
padding: "0.5rem",}}>

<h6>{this.state.error_message_detail}</h6>
{this.state.error_message_detail}
@@ -968,7 +1015,7 @@ renderFileGrabber = () =>{
<Button
onClick={() => this.resetSteps()}
className="btn-lg btn-block m-0 align-self-end"
style={{ padding: "12px" }}
style={{ padding:"12px" }}
variant="contained"
color="primary" >
Restart
@@ -1019,20 +1066,20 @@ renderFileGrabber = () =>{
download
target="_blank "
className="btn-lg btn-block"
style={{ padding: "12px", marginRight: "10px" }}
style={{ padding:"12px", marginRight:"10px" }}
variant="contained"
color="primary" >
{<FontAwesomeIcon
icon={faFileDownload}
className="m-1"
style={{ fontSize: 50 }}
style={{ fontSize:50 }}
/>}
Example.tsv
</Button>
</Typography>
<div className="">
<h4>{toTitleCase(this.props.bulkType).slice(0, -1)} Information Upload</h4>
<Typography className="d-inline-block " style={{ display: "inline-block" }} >
<Typography className="d-inline-block " style={{ display:"inline-block" }} >
To bulk register multiple {this.props.bulkType.toLowerCase()} at one time, upload a tsv file here in the format specified by this example file. <br /> Include one line per {this.props.bulkType.toLowerCase().slice(0, -1)} to register. {toTitleCase(this.props.bulkType).slice(0, -1)} metadata must be provided separately. <br />
<Typography className={this.state.rowLimitClass}> <strong> There is a 40 row limit on uploaded files.</strong> <br /></Typography>