From f121bce02d97be6e268dc2da2ef303189397561a Mon Sep 17 00:00:00 2001 From: Birdmachine Date: Wed, 29 Jan 2025 16:33:27 -0500 Subject: [PATCH 1/2] Provide Feedback to user on Invalid TSV Upload for Contributors and Block Submit on invalid file --- .../components/collections/collections.jsx | 127 +++++++++++++++--- src/src/service/ingest_api.js | 25 ++++ 2 files changed, 135 insertions(+), 17 deletions(-) diff --git a/src/src/components/collections/collections.jsx b/src/src/components/collections/collections.jsx index ca53e197..47ae825d 100644 --- a/src/src/components/collections/collections.jsx +++ b/src/src/components/collections/collections.jsx @@ -4,10 +4,11 @@ import "../../App.css"; import SearchComponent from "../search/SearchComponent"; import {COLUMN_DEF_MIXED,COLUMN_DEF_MIXED_SM} from "../search/table_constants"; import { entity_api_get_entity,entity_api_create_entity, entity_api_update_entity} from '../../service/entity_api'; -import {ingest_api_publish_collection,ingest_api_user_admin} from '../../service/ingest_api'; +import {ingest_api_publish_collection,ingest_api_user_admin, ingest_api_validate_contributors} from '../../service/ingest_api'; import { getPublishStatusColor } from "../../utils/badgeClasses"; import { generateDisplaySubtypeSimple_UBKG } from "../../utils/display_subtypes"; import Papa from 'papaparse'; +import {GridLoader} from "react-spinners"; import ReactTooltip from "react-tooltip"; import { TextField, Button, Box } from '@mui/material'; import Paper from '@material-ui/core/Paper'; @@ -42,31 +43,34 @@ const StyledTextField = styled(TextField)` `; export function CollectionForm (props){ // let navigate = useNavigate(); - var [locked, setLocked] = useState(false); - var [successDialogRender, setSuccessDialogRender] = useState(false); - var [selectedSource, setSelectedSource] = useState(null); // var [selectedGroup, setSlectedGroup] = useState(props.dataGroups[0]).uuid; var [associatedEntities, setassociatedEntities] = useState([]); var [associatedEntitiesInitial, setassociatedEntitiesInitial] = useState([]); - var [selectedSources, setSelectedSources] = useState([]); - var [publishing, setPublishing] = useState(false); - var [fileDetails, setFileDetails] = useState(); var [buttonState, setButtonState] = useState(''); - var [warningOpen, setWarningOpen] = React.useState(false); - var [openGroupModal, setOpenGroupModal] = useState(false); - var [lookupShow, setLookupShow] = useState(false); - var [loadingDatasets, setLoadingDatasets] = useState(true); + var [contributorValidationErrors, setContributorValidationErrors] = useState(''); + var [entityInfo, setEntityInfo] = useState(); + var [fileDetails, setFileDetails] = useState(); var [hideUUIDList, setHideUUIDList] = useState(true); + var [loadingDatasets, setLoadingDatasets] = useState(true); var [loadUUIDList, setLoadUUIDList] = useState(false); - var [validatingSubmitForm, setValidatingSubmitForm] = useState(false); - var [entityInfo, setEntityInfo] = useState(); - var [userAdmin, setUserAdmin] = useState(false); + var [locked, setLocked] = useState(false); + var [disableSubmit, setDisableSubmit] = useState(false); + var [validatingContributorsUpload, setValidatingContributorsUpload] = useState(false); + var [lookupShow, setLookupShow] = useState(false); + var [openGroupModal, setOpenGroupModal] = useState(false); var [pageError, setPageError] = useState(""); + var [publishing, setPublishing] = useState(false); + var [selectedSource, setSelectedSource] = useState(null); + var [selectedSources, setSelectedSources] = useState([]); + var [successDialogRender, setSuccessDialogRender] = useState(false); + var [userAdmin, setUserAdmin] = useState(false); + var [validatingSubmitForm, setValidatingSubmitForm] = useState(false); + var [warningOpen, setWarningOpen] = React.useState(false); // var [publishError, setPublishError] = useState({ // status:"", // message:"", // }); - // @TODO: See what we can globalize/memoize/notize here + // @TODO: See what we can glob alize/memoize/notize here var [errorHandler, setErrorHandler] = useState({ status: "", message: "", @@ -522,6 +526,10 @@ export function CollectionForm (props){ } var handleFileGrab = (e, type) => { + console.debug('%c◉ FILEGRAb ', 'color:#00ff7b', ); + setContributorValidationErrors() + setValidatingContributorsUpload(true) + setDisableSubmit(true); var grabbedFile = e.target.files[0]; var newName = grabbedFile.name.replace(/ /g, '_') var newFile = new File([grabbedFile], newName); @@ -531,6 +539,43 @@ export function CollectionForm (props){ ...prevValues, 'contributors': "", })) + + ingest_api_validate_contributors(JSON.parse(localStorage.getItem("info")).groups_token, newFile) + .then((response) => { + if(response.status === 200){ + console.debug('%c◉ Success ', 'color:#00ff7b', response); + setContributorValidationErrors() + setDisableSubmit(false); + setFormErrors((prevValues) => ({ + ...prevValues, + 'contributors': "", + })) + setValidatingContributorsUpload(false) + }else{ + let errorSet = response.error.response.data.description; + console.debug('%c◉ FAILURE ', 'color:#ff005d', errorSet) + if (errorSet == "metadata_schema_id not found in header") { + setContributorValidationErrors([ + { + "column": "N/A", + "error": "Metadata_schema_id not found in header", + "row": "N/A" + } + ]); + }else{ + setContributorValidationErrors(errorSet); + } + setFormErrors((prevValues) => ({ + ...prevValues, + 'contributors': "Please Review the list of errors provided", + })) + setValidatingContributorsUpload(false) + } + }) + .catch((error) => { + console.debug('%c◉ FAILURE ', 'color:#ff005d', error); + }); + Papa.parse(newFile, { download: true, skipEmptyLines: true, @@ -571,6 +616,22 @@ export function CollectionForm (props){ console.debug("handleUUIDList", name, value, type); }; + var renderValidatingOverlay = () => { + return ( + + + + ) + } + var renderTableRows = (rowDetails) => { if (rowDetails.length > 0) { return rowDetails.map((row, index) => { @@ -597,7 +658,10 @@ export function CollectionForm (props){ var renderContribTable = () => { return ( <> - + {validatingContributorsUpload && ( + renderValidatingOverlay() + )} + @@ -612,10 +676,24 @@ export function CollectionForm (props){ + {} {renderTableRows(formValues.contributors)}
+ {contributorValidationErrors && contributorValidationErrors.length > 0 && ( + + Errors Found:
+ +
+ {/* {renderContributorErrors()} */} +
+ )} + {formErrors.contributors && formErrors.contributors.length > 0 && ( ) } + + var renderContributorErrors = () => { + console.debug('%c◉ contributorValidationErrors ', 'color:#00ff7b', contributorValidationErrors); + let stylizedList = '
    '; + for (const error of contributorValidationErrors) { + console.debug('%c◉ contributorValidationErrors error ', 'color:#00ff7b', error, error,error); + stylizedList += `
  • ${error.error}
  • `; + } + stylizedList += '
'; + + console.log(stylizedList); + return stylizedList; + } + + var renderAssociationTable = () => { var hiddenFields = []; var uniqueTypes = new Set(associatedEntities.map(obj => obj.entity_type.toLowerCase())); @@ -985,7 +1078,7 @@ export function CollectionForm (props){ variant="contained" onClick={() => handleSubmit()} type="button" - disabled={locked} + disabled={locked || disableSubmit} className='float-right'> {buttonState === "submit" && ( { + console.debug("ingest_api_validate_contributors",res); + let results = res.data; + return {status: res.status, results: results} + }) + .catch(error => { + console.debug('%c⭗ ingest_api_validate_contributors', 'color:#ff005d',error ); + // throw new Error(error); + return {error} + }); +}; + From 84a7c76288f47123fdc1c1dac146dda1a6502de0 Mon Sep 17 00:00:00 2001 From: Birdmachine Date: Wed, 29 Jan 2025 17:04:12 -0500 Subject: [PATCH 2/2] Clean up some comments and tabs, apply new Contributors file Validation & Feedback to EPICollections --- .../components/collections/collections.jsx | 1079 ++++++++--------- .../components/collections/epicollections.jsx | 107 +- 2 files changed, 621 insertions(+), 565 deletions(-) diff --git a/src/src/components/collections/collections.jsx b/src/src/components/collections/collections.jsx index 47ae825d..e1b269cc 100644 --- a/src/src/components/collections/collections.jsx +++ b/src/src/components/collections/collections.jsx @@ -221,7 +221,6 @@ export function CollectionForm (props){ setOpenGroupModal(false); } - const handleInputChange = (event) => { const { name, value, type } = event.target; console.debug("handleInputChange", name, value, type); @@ -341,13 +340,6 @@ export function CollectionForm (props){ } - function removeEmpty(obj) { - return Object.fromEntries( - Object.entries(obj) - .filter(([_, v]) => v != null) - .map(([k, v]) => [k, v === Object(v) ? removeEmpty(v) : v]) - ); - } function validateForm(formValues) { console.debug('%c◉ validateForm FormValues ', 'color:#00ff7b', ); var isValid = true; @@ -418,7 +410,6 @@ export function CollectionForm (props){ } } - const handleSubmit = () => { setButtonState("submit"); var submitForm = validateForm(formValues); @@ -523,591 +514,581 @@ export function CollectionForm (props){ setPageError(error.toString()); setButtonState(""); }); - } + } - var handleFileGrab = (e, type) => { - console.debug('%c◉ FILEGRAb ', 'color:#00ff7b', ); - setContributorValidationErrors() - setValidatingContributorsUpload(true) - setDisableSubmit(true); - var grabbedFile = e.target.files[0]; - var newName = grabbedFile.name.replace(/ /g, '_') - var newFile = new File([grabbedFile], newName); - if (newFile && newFile.name.length > 0) { - console.debug('%c◉ HAVE FILE ', 'color:#00ff7b', newFile); - setFormErrors((prevValues) => ({ - ...prevValues, - 'contributors': "", - })) + var handleFileGrab = (e, type) => { + console.debug('%c◉ FILEGRAb ', 'color:#00ff7b', ); + setContributorValidationErrors() + setValidatingContributorsUpload(true) + setDisableSubmit(true); + var grabbedFile = e.target.files[0]; + var newName = grabbedFile.name.replace(/ /g, '_') + var newFile = new File([grabbedFile], newName); + if (newFile && newFile.name.length > 0) { + console.debug('%c◉ HAVE FILE ', 'color:#00ff7b', newFile); + setFormErrors((prevValues) => ({ + ...prevValues, + 'contributors': "", + })) - ingest_api_validate_contributors(JSON.parse(localStorage.getItem("info")).groups_token, newFile) - .then((response) => { - if(response.status === 200){ - console.debug('%c◉ Success ', 'color:#00ff7b', response); - setContributorValidationErrors() - setDisableSubmit(false); - setFormErrors((prevValues) => ({ - ...prevValues, - 'contributors': "", - })) - setValidatingContributorsUpload(false) + ingest_api_validate_contributors(JSON.parse(localStorage.getItem("info")).groups_token, newFile) + .then((response) => { + if(response.status === 200){ + console.debug('%c◉ Success ', 'color:#00ff7b', response); + setContributorValidationErrors() + setDisableSubmit(false); + setFormErrors((prevValues) => ({ + ...prevValues, + 'contributors': "", + })) + setValidatingContributorsUpload(false) + }else{ + let errorSet = response.error.response.data.description; + console.debug('%c◉ FAILURE ', 'color:#ff005d', errorSet) + if (errorSet == "metadata_schema_id not found in header") { + setContributorValidationErrors([ + { + "column": "N/A", + "error": "Metadata_schema_id not found in header", + "row": "N/A" + } + ]); }else{ - let errorSet = response.error.response.data.description; - console.debug('%c◉ FAILURE ', 'color:#ff005d', errorSet) - if (errorSet == "metadata_schema_id not found in header") { - setContributorValidationErrors([ - { - "column": "N/A", - "error": "Metadata_schema_id not found in header", - "row": "N/A" - } - ]); - }else{ - setContributorValidationErrors(errorSet); - } - setFormErrors((prevValues) => ({ - ...prevValues, - 'contributors': "Please Review the list of errors provided", - })) - setValidatingContributorsUpload(false) + setContributorValidationErrors(errorSet); } - }) - .catch((error) => { - console.debug('%c◉ FAILURE ', 'color:#ff005d', error); - }); - - Papa.parse(newFile, { - download: true, - skipEmptyLines: true, - header: true, - complete: data => { - setFileDetails({ - ...fileDetails, - [type]: data.data - }); - processContacts(data,"grab") + setFormErrors((prevValues) => ({ + ...prevValues, + 'contributors': "Please Review the list of errors provided", + })) + setValidatingContributorsUpload(false) } + }) + .catch((error) => { + console.debug('%c◉ FAILURE ', 'color:#ff005d', error); }); - } else { - console.debug("No Data??"); - } - }; - var processContacts = (data,source) => { - var contributors = [] - var contacts = [] - for (const row of data.data) { - contributors.push(row) - if(!row.is_contact){ - row.is_contact = "NO" - }else if (row.is_contact && (row.is_contact === "TRUE"|| row.is_contact.toLowerCase()==="yes") ){ - contacts.push(row) - } + Papa.parse(newFile, { + download: true, + skipEmptyLines: true, + header: true, + complete: data => { + setFileDetails({ + ...fileDetails, + [type]: data.data + }); + processContacts(data,"grab") } - setFormValues ({ - ...formValues, - contacts: contacts, - contributors: contributors - }); - } - - var processUUIDs = (event) => { - const { name, value, type } = event.target; - console.debug("handleUUIDList", name, value, type); - }; - - var renderValidatingOverlay = () => { - return ( - - - - ) + }); + } else { + console.debug("No Data??"); } + }; - var renderTableRows = (rowDetails) => { - if (rowDetails.length > 0) { - return rowDetails.map((row, index) => { - return ( - - {row.display_name} - {row.affiliation} - {row.orcid} - {row.email } - { (row.is_contact && (row.is_contact==="TRUE" || row.is_contact.toLowerCase()==="yes")) ? : ""} - {row.is_principal_investigator } - {row.is_operator } - {row.metadata_schema_id} - - ); - }); + var processContacts = (data,source) => { + var contributors = [] + var contacts = [] + for (const row of data.data) { + contributors.push(row) + if(!row.is_contact){ + row.is_contact = "NO" + }else if (row.is_contact && (row.is_contact === "TRUE"|| row.is_contact.toLowerCase()==="yes") ){ + contacts.push(row) + } } - } - - - var renderContribTable = () => { - return ( - <> - {validatingContributorsUpload && ( - renderValidatingOverlay() - )} - - - - - Name - Affiliation - Orcid - Email - Is Contact - Is Principal Investigator - Is Operator - Metadata Schema ID - - - - {} - {renderTableRows(formValues.contributors)} - -
-
- {contributorValidationErrors && contributorValidationErrors.length > 0 && ( - - Errors Found:
+ setFormValues ({ + ...formValues, + contacts: contacts, + contributors: contributors + }); + } -
- {/* {renderContributorErrors()} */} -
- )} - - {formErrors.contributors && formErrors.contributors.length > 0 && ( - - {formErrors.contributors} - - )} - - ) - } + var processUUIDs = (event) => { + const { name, value, type } = event.target; + console.debug("handleUUIDList", name, value, type); + }; - var renderContributorErrors = () => { - console.debug('%c◉ contributorValidationErrors ', 'color:#00ff7b', contributorValidationErrors); - let stylizedList = '
    '; - for (const error of contributorValidationErrors) { - console.debug('%c◉ contributorValidationErrors error ', 'color:#00ff7b', error, error,error); - stylizedList += `
  • ${error.error}
  • `; - } - stylizedList += '
'; + var renderValidatingOverlay = () => { + return ( + + + + ) + } - console.log(stylizedList); - return stylizedList; + var renderTableRows = (rowDetails) => { + if (rowDetails.length > 0) { + return rowDetails.map((row, index) => { + return ( + + {row.display_name} + {row.affiliation} + {row.orcid} + {row.email } + { (row.is_contact && (row.is_contact==="TRUE" || row.is_contact.toLowerCase()==="yes")) ? : ""} + {row.is_principal_investigator } + {row.is_operator } + {row.metadata_schema_id} + + ); + }); } + } - var renderAssociationTable = () => { - var hiddenFields = []; - var uniqueTypes = new Set(associatedEntities.map(obj => obj.entity_type.toLowerCase())); - if ( (uniqueTypes.has("dataset") && uniqueTypes.size === 1) ) { - // add submission_id to hiddenFields - hiddenFields.push("submission_id"); - } - function buildColumnFilter(arr) { - let obj = {}; - arr.forEach(value => { - obj[value] = false; - }); - return obj; + var renderContribTable = () => { + return ( + <> + {validatingContributorsUpload && ( + renderValidatingOverlay() + )} + + + + + Name + Affiliation + Orcid + Email + Is Contact + Is Principal Investigator + Is Operator + Metadata Schema ID + + + + {} + {renderTableRows(formValues.contributors)} + +
+
+ {contributorValidationErrors && contributorValidationErrors.length > 0 && ( + + Errors Found:
+
+
+ )} + + {formErrors.contributors && formErrors.contributors.length > 0 && ( + + {formErrors.contributors} + + )} + + ) + } + + var renderContributorErrors = () => { + let stylizedList = '
    '; + for (const error of contributorValidationErrors) { + stylizedList += `
  • ${error.error}
  • `; } - var columnFilters = buildColumnFilter(hiddenFields) + stylizedList += '
'; + return stylizedList; + } - return ( -
- 0 && !isNew} - sx={{ - // minHeight: '200px', - // display: 'inline-block', - // // overflow: 'auto', - // '.MuiDataGrid-virtualScroller': { - // minHeight: '45px', - // // overflow: 'scroll', - // }, - '.MuiDataGrid-main > .MuiDataGrid-virtualScroller': { - minHeight: '60px', - // overflowY: 'auto !important', - // flex: 'unset !important', - }, - }} - /> -
- ); - } - var creationSuccess = (response) => { - var resultInfo = { - entity: response.results - }; - setEntityInfo(resultInfo); - props.onProcessed(resultInfo) + var renderAssociationTable = () => { + var hiddenFields = []; + var uniqueTypes = new Set(associatedEntities.map(obj => obj.entity_type.toLowerCase())); + if ( (uniqueTypes.has("dataset") && uniqueTypes.size === 1) ) { + // add submission_id to hiddenFields + hiddenFields.push("submission_id"); } - - var formatDatatype = (row) => { - console.debug('%c⊙', 'color:#00ff7b', "formatDatatype", row, row.display_subtype, row.dataset_type); - return ("DT"); + function buildColumnFilter(arr) { + let obj = {}; + arr.forEach(value => { + obj[value] = false; + }); + return obj; } + var columnFilters = buildColumnFilter(hiddenFields) return ( - -
+
+ 0 && !isNew} + sx={{ + // minHeight: '200px', + // display: 'inline-block', + // // overflow: 'auto', + // '.MuiDataGrid-virtualScroller': { + // minHeight: '45px', + // // overflow: 'scroll', + // }, + '.MuiDataGrid-main > .MuiDataGrid-virtualScroller': { + minHeight: '60px', + // overflowY: 'auto !important', + // flex: 'unset !important', + }, + }} + /> +
+ ); + } -
-
-

- {!props.newForm && editingCollection && ( - - HuBMAP Collection ID: {editingCollection.hubmap_id} - {" "} - - )} - {(props.newForm) && ( - - Registering a Collection - - )} -

- {!props.newForm && ( -
{props.editingCollection.title}
+ var creationSuccess = (response) => { + var resultInfo = { + entity: response.results + }; + setEntityInfo(resultInfo); + props.onProcessed(resultInfo) + } + + var formatDatatype = (row) => { + console.debug('%c⊙', 'color:#00ff7b', "formatDatatype", row, row.display_subtype, row.dataset_type); + return ("DT"); + } + + return ( + +
+ +
+
+

+ {!props.newForm && editingCollection && ( + + HuBMAP Collection ID: {editingCollection.hubmap_id} + {" "} + )} - {editingCollection && editingCollection.doi_url && ( -

- doi: {editingCollection.doi_url} -

+ {(props.newForm) && ( + + Registering a Collection + )} -
+ + {!props.newForm && ( +
{props.editingCollection.title}
+ )} + {editingCollection && editingCollection.doi_url && ( +

+ doi: {editingCollection.doi_url} +

+ )}
+
- - - -

- The source tissue samples or data from which this data was derived.
- At least one source is required, but multiple may be specified. -

-
+ + + +

+ The source tissue samples or data from which this data was derived.
+ At least one source is required, but multiple may be specified. +

+
+ + {loadingDatasets && ( + + )} - {loadingDatasets && ( - - )} - + + {!loadingDatasets && (<> - {!loadingDatasets && (<> - - {renderAssociationTable()} - - {formErrors.bulk_dataset_uuids[0].length > 0 && ( - - Error: {formErrors.bulk_dataset_uuids[1]}: {formErrors.bulk_dataset_uuids[2]} ({formErrors.bulk_dataset_uuids[2]}) + {renderAssociationTable()} + + {formErrors.bulk_dataset_uuids[0].length > 0 && ( + + Error: {formErrors.bulk_dataset_uuids[1]}: {formErrors.bulk_dataset_uuids[2]} ({formErrors.bulk_dataset_uuids[2]}) + + )} + {formWarnings.bulk_dataset_uuids.length > 0 && ( + + {setWarningOpen(false)}}> + + }> + Notice: {formWarnings.bulk_dataset_uuids} - )} - {formWarnings.bulk_dataset_uuids.length > 0 && ( - - {setWarningOpen(false)}}> - - }> - Notice: {formWarnings.bulk_dataset_uuids} - - - - )} + + + )} - - - - - - + + + - - - {loadUUIDList && ( - - )} - {!loadUUIDList && ( - handleInputUUIDs(event)} + > + {hideUUIDList && (<>Bulk)} + {!hideUUIDList && (<>Add)} + + + + + + + {loadUUIDList && ( + + )} + {!loadUUIDList && ( + + 0 ? true : false} + disabled={locked} + multiline + rows={2} + inputProps={{ 'aria-label': 'description' }} + placeholder={"List of Dataset HuBMAP IDs or UUIDs, Comma Seperated "} + variant="standard" + size="small" + fullWidth={true} + onChange={(event) => handleInputChange(event)} + value={formValues.dataset_uuids} sx={{ + marginTop: '10px', + width: '100%', verticalAlign: 'bottom', - minWidth: "400px", - overflow: 'hidden', - // display: 'flex', - // flexDirection: 'row', - }}> - 0 ? true : false} - disabled={locked} - multiline - rows={2} - inputProps={{ 'aria-label': 'description' }} - placeholder={"List of Dataset HuBMAP IDs or UUIDs, Comma Seperated "} - variant="standard" - size="small" - fullWidth={true} - onChange={(event) => handleInputChange(event)} - value={formValues.dataset_uuids} - sx={{ - marginTop: '10px', - width: '100%', - verticalAlign: 'bottom', - }} - /> - - )} - - - - {!hideUUIDList && ( - - {setHideUUIDList(true)}}> - - )} - + }} + /> + + )} + - {formErrors.dataset_uuids && formErrors.dataset_uuids.length > 0 && ( - - {formErrors.dataset_uuids} + + {!hideUUIDList && ( + + {setHideUUIDList(true)}}> )} - - )} - - - setLookupShow(false)} - aria-labelledby="association-lookup-dialog" - open={lookupShow}> - - handleSelectClick(e)} - custom_title="Search for an Associated Dataset for your Collection" - // filter_type="Publication" - modecheck="Source" - restrictions={{ - entityType: "dataset" - }} - /> - - - - - -
+ + + {formErrors.dataset_uuids && formErrors.dataset_uuids.length > 0 && ( + + {formErrors.dataset_uuids} + + )} + + )} - - 0 ? true : false} - disabled={false} - helperText={formErrors.title && formErrors.title.length > 0 ? "The title of the Collection is Required" : "The title of the Collection" } - variant="standard" - onChange={handleInputChange} - value={formValues.title} - /> - + + setLookupShow(false)} + aria-labelledby="association-lookup-dialog" + open={lookupShow}> + + handleSelectClick(e)} + custom_title="Search for an Associated Dataset for your Collection" + // filter_type="Publication" + modecheck="Source" + restrictions={{ + entityType: "dataset" + }} + /> + + + + + +
- - 0 ? true : false} - disabled={false} - helperText={formErrors.title && formErrors.title.length > 0 ? "A description of the Collection is Required" : "A description of the Collection" } - variant="standard" - onChange={handleInputChange} - value={formValues.description} - /> - - - - Contributors - - {formValues.contributors && formValues.contributors.length > 0 && ( - <>{renderContribTable()} - )} -
- Please refer to the contributor file schema information, and this Example TSV File -
-
- -
-
+ + 0 ? true : false} + disabled={false} + helperText={formErrors.title && formErrors.title.length > 0 ? "The title of the Collection is Required" : "The title of the Collection" } + variant="standard" + onChange={handleInputChange} + value={formValues.title} + /> + - {pageError.length > 0 && ( -
- - Error: {pageError} - -
+ + 0 ? true : false} + disabled={false} + helperText={formErrors.title && formErrors.title.length > 0 ? "A description of the Collection is Required" : "A description of the Collection" } + variant="standard" + onChange={handleInputChange} + value={formValues.description} + /> + + + + Contributors + + {formValues.contributors && formValues.contributors.length > 0 && ( + <>{renderContribTable()} )} +
+ Please refer to the contributor file schema information, and this Example TSV File +
+
+ +
+
+ {pageError.length > 0 && (
-
- {userAdmin === true && (editingCollection && !editingCollection.doi_url) && ( - handlePublish()} - variant="contained"> - Publish - - )} + + Error: {pageError} + +
+ )} - - -
+
+
+ {userAdmin === true && (editingCollection && !editingCollection.doi_url) && ( + handlePublish()} + variant="contained"> + Publish + + )} + + +
+
- handleCreate(validatingSubmitForm)} - hide={hideGroupModal} - handleInputChange={(event) => handleInputChange(event)} - /> - - - ); - } \ No newline at end of file + handleCreate(validatingSubmitForm)} + hide={hideGroupModal} + handleInputChange={(event) => handleInputChange(event)} + /> + + + ); +} \ No newline at end of file diff --git a/src/src/components/collections/epicollections.jsx b/src/src/components/collections/epicollections.jsx index fab099ef..30d38af9 100644 --- a/src/src/components/collections/epicollections.jsx +++ b/src/src/components/collections/epicollections.jsx @@ -4,10 +4,11 @@ import "../../App.css"; import SearchComponent from "../search/SearchComponent"; import {COLUMN_DEF_MIXED,COLUMN_DEF_MIXED_SM,COLUMN_DEF_COLLECTION} from "../search/table_constants"; import { entity_api_get_entity,entity_api_create_entity, entity_api_update_entity} from '../../service/entity_api'; -import {ingest_api_publish_collection,ingest_api_user_admin} from '../../service/ingest_api'; +import {ingest_api_publish_collection,ingest_api_user_admin,ingest_api_validate_contributors} from '../../service/ingest_api'; import { getPublishStatusColor } from "../../utils/badgeClasses"; import { generateDisplaySubtypeSimple_UBKG } from "../../utils/display_subtypes"; import Papa from 'papaparse'; +import {GridLoader} from "react-spinners"; import ReactTooltip from "react-tooltip"; import { TextField, Button, Box } from '@mui/material'; import Paper from '@material-ui/core/Paper'; @@ -44,27 +45,30 @@ const StyledTextField = styled(TextField)` `; export function EPICollectionForm (props){ // let navigate = useNavigate(); - var [locked, setLocked] = useState(false); - var [successDialogRender, setSuccessDialogRender] = useState(false); - var [selectedSource, setSelectedSource] = useState(null); // var [selectedGroup, setSlectedGroup] = useState(props.dataGroups[0]).uuid; var [associatedEntities, setassociatedEntities] = useState([]); var [associatedEntitiesInitial, setassociatedEntitiesInitial] = useState([]); - var [selectedSources, setSelectedSources] = useState([]); - var [fileDetails, setFileDetails] = useState(); var [buttonState, setButtonState] = useState(''); - var [warningOpen, setWarningOpen] = React.useState(false); - var [openGroupModal, setOpenGroupModal] = useState(false ); - var [lookupShow, setLookupShow] = useState(false); - var [loadingDatasets, setLoadingDatasets] = useState(true); + var [contributorValidationErrors, setContributorValidationErrors] = useState(''); + var [entityInfo, setEntityInfo] = useState(); + var [fileDetails, setFileDetails] = useState(); var [hideUUIDList, setHideUUIDList] = useState(true); + var [loadingDatasets, setLoadingDatasets] = useState(true); var [loadUUIDList, setLoadUUIDList] = useState(false); - var [validatingSubmitForm, setValidatingSubmitForm] = useState(false); - var [entityInfo, setEntityInfo] = useState(); - var [userAdmin, setUserAdmin] = useState(false); + var [locked, setLocked] = useState(false); + var [disableSubmit, setDisableSubmit] = useState(false); + var [validatingContributorsUpload, setValidatingContributorsUpload] = useState(false); + var [lookupShow, setLookupShow] = useState(false); + var [openGroupModal, setOpenGroupModal] = useState(false ); var [pageError, setPageError] = useState(""); var [publishing, setPublishing] = useState(false); -// var [publishError, setPublishError] = useState({ + var [selectedSource, setSelectedSource] = useState(null); + var [selectedSources, setSelectedSources] = useState([]); + var [successDialogRender, setSuccessDialogRender] = useState(false); + var [userAdmin, setUserAdmin] = useState(false); + var [validatingSubmitForm, setValidatingSubmitForm] = useState(false); + var [warningOpen, setWarningOpen] = React.useState(false); + // var [publishError, setPublishError] = useState({ // status:"", // message:"", // }); @@ -534,6 +538,8 @@ export function EPICollectionForm (props){ } var handleFileGrab = (e, type) => { + setValidatingContributorsUpload(true) + setDisableSubmit(true); var grabbedFile = e.target.files[0]; var newName = grabbedFile.name.replace(/ /g, '_') var newFile = new File([grabbedFile], newName); @@ -543,6 +549,42 @@ export function EPICollectionForm (props){ ...prevValues, 'contributors': "", })) + ingest_api_validate_contributors(JSON.parse(localStorage.getItem("info")).groups_token, newFile) + .then((response) => { + if(response.status === 200){ + console.debug('%c◉ Success ', 'color:#00ff7b', response); + setContributorValidationErrors() + setDisableSubmit(false); + setFormErrors((prevValues) => ({ + ...prevValues, + 'contributors': "", + })) + setValidatingContributorsUpload(false) + }else{ + let errorSet = response.error.response.data.description; + console.debug('%c◉ FAILURE ', 'color:#ff005d', errorSet) + if (errorSet == "metadata_schema_id not found in header") { + setContributorValidationErrors([ + { + "column": "N/A", + "error": "Metadata_schema_id not found in header", + "row": "N/A" + } + ]); + }else{ + setContributorValidationErrors(errorSet); + } + setFormErrors((prevValues) => ({ + ...prevValues, + 'contributors': "Please Review the list of errors provided", + })) + setValidatingContributorsUpload(false) + } + }) + .catch((error) => { + console.debug('%c◉ FAILURE ', 'color:#ff005d', error); + }); + Papa.parse(newFile, { download: true, skipEmptyLines: true, @@ -584,6 +626,22 @@ export function EPICollectionForm (props){ console.debug("handleUUIDList", name, value, type); }; + var renderValidatingOverlay = () => { + return ( + + + + ) + } + var renderTableRows = (rowDetails) => { if (rowDetails.length > 0) { return rowDetails.map((row, index) => { @@ -609,8 +667,10 @@ export function EPICollectionForm (props){ var renderContribTable = () => { return ( - <> + {validatingContributorsUpload && ( + renderValidatingOverlay() + )} @@ -630,6 +690,12 @@ export function EPICollectionForm (props){
+ {contributorValidationErrors && contributorValidationErrors.length > 0 && ( + + Errors Found:
+
+
+ )} {formErrors.contributors && formErrors.contributors.length > 0 && ( { + let stylizedList = '
    '; + for (const error of contributorValidationErrors) { + stylizedList += `
  • ${error.error}
  • `; + } + stylizedList += '
'; + return stylizedList; + } + var renderAssociationTable = () => { var hiddenFields = ["registered_doi"]; var uniqueTypes = new Set(associatedEntities.map(obj => obj.entity_type.toLowerCase())); @@ -1005,7 +1080,7 @@ export function EPICollectionForm (props){ variant="contained" onClick={() => handleSubmit()} type="button" - disabled={locked} + disabled={locked || disableSubmit} className='float-right'> {buttonState === "submit" && (