diff --git a/src/components/DataSubmissions/DataSubmissionUpload.tsx b/src/components/DataSubmissions/DataSubmissionUpload.tsx index 44c0e45a..9a89f013 100644 --- a/src/components/DataSubmissions/DataSubmissionUpload.tsx +++ b/src/components/DataSubmissions/DataSubmissionUpload.tsx @@ -114,10 +114,11 @@ const UploadRoles: User["role"][] = ["Organization Owner"]; // and submission ow type Props = { submitterID: string; readOnly?: boolean; + onCreateBatch: () => void; onUpload: (message: string, severity: VariantType) => void; }; -const DataSubmissionUpload = ({ submitterID, readOnly, onUpload }: Props) => { +const DataSubmissionUpload = ({ submitterID, readOnly, onCreateBatch, onUpload }: Props) => { const { submissionId } = useParams(); const { user } = useAuthContext(); @@ -228,6 +229,7 @@ const DataSubmissionUpload = ({ submitterID, readOnly, onUpload }: Props) => { if (!newBatch) { return; } + onCreateBatch(); const uploadResult: UploadResult[] = []; diff --git a/src/content/dataSubmissions/DataSubmission.tsx b/src/content/dataSubmissions/DataSubmission.tsx index c06a573f..b770cdd5 100644 --- a/src/content/dataSubmissions/DataSubmission.tsx +++ b/src/content/dataSubmissions/DataSubmission.tsx @@ -293,7 +293,7 @@ const columns: Column[] = [ } }, { - label: "Errors", + label: "Upload Errors", renderValue: (data) => ( {({ handleOpenErrorDialog }) => { @@ -322,7 +322,7 @@ const columns: Column[] = [ const URLTabs = { DATA_UPLOAD: "data-upload", - QUALITY_CONTROL: "quality-control" + VALIDATION_RESULTS: "validation-results" }; const submissionLockedStatuses: SubmissionStatus[] = ["Submitted", "Released", "Completed", "Canceled", "Archived"]; @@ -531,10 +531,10 @@ const DataSubmission = () => { selected={tab === URLTabs.DATA_UPLOAD} /> @@ -544,6 +544,7 @@ const DataSubmission = () => { a.dataCommons, field: "dataCommons", }, + { + label: "DM Versison", + value: (a) => a.modelVersion, + field: "modelVersion", + }, { label: "Organization", value: (a) => a.organization.name, diff --git a/src/content/dataSubmissions/ErrorDialog.tsx b/src/content/dataSubmissions/ErrorDialog.tsx index 6a083b6d..baf9b384 100644 --- a/src/content/dataSubmissions/ErrorDialog.tsx +++ b/src/content/dataSubmissions/ErrorDialog.tsx @@ -69,6 +69,7 @@ const StyledTitle = styled(Typography)({ fontStyle: "normal", fontWeight: "900", lineHeight: "30px", + paddingBottom: "8px" }); const StyledUploadedDate = styled(Typography)({ @@ -78,8 +79,6 @@ const StyledUploadedDate = styled(Typography)({ fontStyle: "normal", fontWeight: 400, lineHeight: "19.6px", - marginTop: "8px", - marginBottom: "35px" }); const StyledSubtitle = styled(Typography)({ @@ -91,6 +90,7 @@ const StyledSubtitle = styled(Typography)({ lineHeight: "20px", letterSpacing: "0.14px", textTransform: "uppercase", + marginTop: "35px" }); const StyledErrorItem = styled(Typography)({ @@ -113,7 +113,8 @@ type Props = { title?: string; closeText?: string; errors: string[]; - uploadedDate: string; + errorCount?: string; + uploadedDate?: string; onClose?: () => void; } & Omit; @@ -122,6 +123,7 @@ const ErrorDialog = ({ title, closeText = "Close", errors, + errorCount, uploadedDate, onClose, open, @@ -138,20 +140,24 @@ const ErrorDialog = ({ - - {header} - + {header && ( + + {header} + + )} {title} - - Uploaded on - {" "} - {FormatDate(uploadedDate, "M/D/YYYY", "N/A")} - + {uploadedDate && ( + + Uploaded on + {" "} + {FormatDate(uploadedDate, "M/D/YYYY", "N/A")} + + )} - {`${errors?.length || 0} ${errors?.length === 1 ? "ERROR" : "ERRORS"}`} + {errorCount || `${errors?.length || 0} ${errors?.length === 1 ? "ERROR" : "ERRORS"}`} {errors?.map((error: string, idx: number) => ( diff --git a/src/content/dataSubmissions/QualityControl.tsx b/src/content/dataSubmissions/QualityControl.tsx index 2bd36d0c..8c66811c 100644 --- a/src/content/dataSubmissions/QualityControl.tsx +++ b/src/content/dataSubmissions/QualityControl.tsx @@ -6,7 +6,7 @@ import { Box, Button, FormControl, MenuItem, Select, styled } from "@mui/materia import { Controller, useForm } from 'react-hook-form'; import { LIST_BATCHES, LIST_NODE_TYPES, ListBatchesResp, ListNodeTypesResp, SUBMISSION_QC_RESULTS, SubmissionQCResultsResp } from "../../graphql"; import GenericTable, { Column, FetchListing, TableMethods } from "../../components/DataSubmissions/GenericTable"; -import { FormatDate } from "../../utils"; +import { FormatDate, capitalizeFirstLetter } from "../../utils"; import ErrorDialog from "./ErrorDialog"; import QCResultsContext from "./Contexts/QCResultsContext"; @@ -103,14 +103,20 @@ const StyledSelect = styled(Select)(baseTextFieldStyles); const columns: Column[] = [ { - label: "Type", + label: "Batch ID", + renderValue: (data) => {data?.displayID}, + field: "displayID", + default: true + }, + { + label: "Node Type", renderValue: (data) => {data?.nodeType}, field: "nodeType", }, { - label: "Batch ID", - renderValue: (data) => {data?.displayID}, - field: "displayID", + label: "Validation Type", + renderValue: (data) => {data?.validationType}, + field: "validationType", }, { label: "Node ID", @@ -128,18 +134,17 @@ const columns: Column[] = [ field: "severity", }, { - label: "Uploaded Date", - renderValue: (data) => (data?.uploadedDate ? `${FormatDate(data.uploadedDate, "MM-DD-YYYY [at] hh:mm A")}` : ""), - field: "uploadedDate", - default: true + label: "Validated Date", + renderValue: (data) => (data?.validatedDate ? `${FormatDate(data?.validatedDate, "MM-DD-YYYY [at] hh:mm A")}` : ""), + field: "validatedDate", }, { - label: "Reasons", - renderValue: (data) => data?.description?.length > 0 && ( + label: "Issues", + renderValue: (data) => (data?.errors?.length > 0 || data?.warnings?.length > 0) && ( {({ handleOpenErrorDialog }) => ( <> - {data.description[0]?.title} + {data.errors?.length > 0 ? data.errors[0].title : data.warnings[0]?.title } {" "} handleOpenErrorDialog && handleOpenErrorDialog(data)} @@ -154,7 +159,6 @@ const columns: Column[] = [ )} ), - field: "description", sortDisabled: true, }, ]; @@ -173,6 +177,10 @@ const QualityControl: FC = () => { const [selectedRow, setSelectedRow] = useState(null); const tableRef = useRef(null); + const errorDescriptions = selectedRow?.errors?.map((error) => `(Error) ${error.description}`) ?? []; + const warningDescriptions = selectedRow?.warnings?.map((warning) => `(Warning) ${warning.description}`) ?? []; + const allDescriptions = [...errorDescriptions, ...warningDescriptions]; + const [submissionQCResults] = useLazyQuery(SUBMISSION_QC_RESULTS, { variables: { id: submissionId }, context: { clientName: 'backend' }, @@ -332,6 +340,7 @@ const QualityControl: FC = () => { total={totalData || 0} loading={loading} defaultRowsPerPage={20} + defaultOrder="desc" setItemKey={(item, idx) => `${idx}_${item.batchID}_${item.nodeID}`} onFetchData={handleFetchQCResults} /> @@ -339,10 +348,10 @@ const QualityControl: FC = () => { setOpenErrorDialog(false)} - header="Data Submission" - title="Reasons" - errors={selectedRow?.description?.map((error) => error.description)} - uploadedDate={selectedRow?.uploadedDate} + header={null} + title={`Validation Issues for ${capitalizeFirstLetter(selectedRow?.nodeType)} Node ID ${selectedRow?.nodeID}.`} + errors={allDescriptions} + errorCount={`${allDescriptions?.length || 0} ${allDescriptions?.length === 1 ? "ISSUE" : "ISSUES"}`} /> ); diff --git a/src/graphql/listSubmissions.ts b/src/graphql/listSubmissions.ts index ac918e0d..38d0854d 100644 --- a/src/graphql/listSubmissions.ts +++ b/src/graphql/listSubmissions.ts @@ -15,6 +15,7 @@ export const query = gql` dataCommons studyAbbreviation dbGaPID + modelVersion status conciergeName createdAt diff --git a/src/graphql/submissionQCResults.ts b/src/graphql/submissionQCResults.ts index 4b0ac281..6ec05ff0 100644 --- a/src/graphql/submissionQCResults.ts +++ b/src/graphql/submissionQCResults.ts @@ -25,13 +25,19 @@ export const query = gql` results { submissionID nodeType + validationType batchID displayID nodeID CRDC_ID severity uploadedDate - description { + validatedDate + errors { + title + description + } + warnings { title description } diff --git a/src/types/Submissions.d.ts b/src/types/Submissions.d.ts index 8f45a225..074374ce 100644 --- a/src/types/Submissions.d.ts +++ b/src/types/Submissions.d.ts @@ -175,13 +175,16 @@ type QCResults = { type QCResult = { submissionID: string; nodeType: string; + validationType: "metadata" | "file"; // [metadata, file] batchID: string; displayID: number; nodeID: string; CRDC_ID: string; severity: "Error" | "Warning"; // [Error, Warning] - uploadedDate: string // batch.updatedAt - description: ErrorMessage[]; + uploadedDate: string; // batch.updatedAt + validatedDate: string; + errors: ErrorMessage[]; + warnings: ErrorMessage[]; }; type ErrorMessage = { diff --git a/src/utils/stringUtils.ts b/src/utils/stringUtils.ts index 095049df..a7907840 100644 --- a/src/utils/stringUtils.ts +++ b/src/utils/stringUtils.ts @@ -1,3 +1,12 @@ +/** + * Capitalizes the first letter of a given string. + * If the input string is empty, it returns an empty string. + * + * @param {string} str - The string to capitalize. + * @returns {string} - The capitalized string or an empty string if the input is empty. + */ +export const capitalizeFirstLetter = (str: string): string => (str ? str[0].toUpperCase() + str.slice(1) : ""); + /** * Function to add a space between a number and a letter in a string. * @param input - The input string to be processed. It should be a string where a number is directly followed by a letter.