From 962102398507f0760ec586b5d9a351d3b8dc7880 Mon Sep 17 00:00:00 2001 From: Michael Tang Date: Wed, 15 Nov 2023 13:50:39 -0500 Subject: [PATCH 01/11] frontend almost done minus styling --- src/components/DataSubmissions/RadioInput.tsx | 52 +----- .../Questionnaire/RadioYesNoInput.tsx | 54 +----- .../Questionnaire/StyledRadioButton.tsx | 53 ++++++ .../dataSubmissions/DataSubmission.tsx | 169 +++++++++++++++++- 4 files changed, 226 insertions(+), 102 deletions(-) create mode 100644 src/components/Questionnaire/StyledRadioButton.tsx diff --git a/src/components/DataSubmissions/RadioInput.tsx b/src/components/DataSubmissions/RadioInput.tsx index f9963b7b..4adf39eb 100644 --- a/src/components/DataSubmissions/RadioInput.tsx +++ b/src/components/DataSubmissions/RadioInput.tsx @@ -1,7 +1,8 @@ import React, { FC, useState, useRef, useEffect } from 'react'; -import { Grid, FormControl, FormControlLabel, Radio, RadioGroup, RadioProps, RadioGroupProps, FormHelperText, Stack } from '@mui/material'; +import { Grid, FormControl, FormControlLabel, RadioGroup, RadioGroupProps, FormHelperText, Stack } from '@mui/material'; import styled from "styled-components"; import { updateInputValidity } from '../../utils'; +import StyledRadioButton from '../Questionnaire/StyledRadioButton'; const GridStyled = styled(Grid)<{ $containerWidth?: string; }>` width: ${(props) => props.$containerWidth}; @@ -34,30 +35,6 @@ const GridStyled = styled(Grid)<{ $containerWidth?: string; }>` } `; -const BpIcon = styled('span')(() => ({ - borderRadius: '50%', - width: 24, - height: 24, - outline: '6px auto #1D91AB', - 'input:hover ~ &': { - outlineOffset: "2px", - }, -})); - -const BpCheckedIcon = styled(BpIcon)({ - backgroundImage: 'linear-gradient(180deg,hsla(0,0%,100%,.1),hsla(0,0%,100%,0))', - '&:before': { - borderRadius: "50%", - display: 'block', - marginTop: "4px", - marginLeft: "4px", - width: 16, - height: 16, - backgroundImage: 'radial-gradient(#1D91AB, #1D91AB)', - content: '""', - }, -}); - const StyledFormLabel = styled("label")(() => ({ fontWeight: 700, fontSize: "16px", @@ -71,15 +48,6 @@ const StyledAsterisk = styled("span")(() => ({ color: "#D54309", })); -const StyledRadio = styled(Radio)((props) => ({ - "& input": { - cursor: props.readOnly ? "not-allowed" : "initial", - }, - "& .radio-icon": { - backgroundColor: props.readOnly ? "#D2DFE9 !important" : "initial", - } -})); - const StyledFormControlLabel = styled(FormControlLabel)(() => ({ "&.MuiFormControlLabel-root": { paddingRight: "10px", @@ -95,20 +63,6 @@ const StyledFormControlLabel = styled(FormControlLabel)(() => ({ } })); -// Inspired by blueprintjs -const BpRadio = (props: RadioProps) => ( - } - icon={} - inputProps={{ - "data-type": "auto" - } as unknown} - {...props} - /> -); - type Option = { label: string; value: string; @@ -199,7 +153,7 @@ const RadioYesNoInput: FC = ({ label={option.label} color="#1D91AB" control={( - ` width: ${(props) => props.$containerWidth}; @@ -34,30 +35,6 @@ const GridStyled = styled(Grid)<{ $containerWidth?: string; }>` } `; -const BpIcon = styled('span')(() => ({ - borderRadius: '50%', - width: 24, - height: 24, - outline: '6px auto #1D91AB', - 'input:hover ~ &': { - outlineOffset: "2px", - }, -})); - -const BpCheckedIcon = styled(BpIcon)({ - backgroundImage: 'linear-gradient(180deg,hsla(0,0%,100%,.1),hsla(0,0%,100%,0))', - '&:before': { - borderRadius: "50%", - display: 'block', - marginTop: "4px", - marginLeft: "4px", - width: 16, - height: 16, - backgroundImage: 'radial-gradient(#1D91AB, #1D91AB)', - content: '""', - }, -}); - const StyledFormLabel = styled("label")(() => ({ fontWeight: 700, fontSize: "16px", @@ -72,29 +49,6 @@ const StyledAsterisk = styled("span")(() => ({ color: "#D54309", })); -const StyledRadio = styled(Radio)((props) => ({ - "& input": { - cursor: props.readOnly ? "not-allowed" : "initial", - }, - "& .radio-icon": { - backgroundColor: props.readOnly ? "#E5EEF4 !important" : "initial", - } -})); - -// Inspired by blueprintjs -const BpRadio = (props: RadioProps) => ( - } - icon={} - inputProps={{ - "data-type": "auto" - } as unknown} - {...props} - /> -); - type Props = { label: string; name: string; @@ -164,8 +118,8 @@ const RadioYesNoInput: FC = ({ data-type="string" {...rest} > - } label="Yes" /> - } label="No" /> + } label="Yes" /> + } label="No" /> {(!readOnly && error ? "This field is required" : null) || " "} diff --git a/src/components/Questionnaire/StyledRadioButton.tsx b/src/components/Questionnaire/StyledRadioButton.tsx new file mode 100644 index 00000000..575c7ddd --- /dev/null +++ b/src/components/Questionnaire/StyledRadioButton.tsx @@ -0,0 +1,53 @@ +import { + Radio, + RadioProps, + + styled, + } from "@mui/material"; + +const StyledRadio = styled(Radio)((props) => ({ + "& input": { + cursor: props.readOnly ? "not-allowed" : "initial", + }, + "& .radio-icon": { + background: (props.readOnly || props.disabled) ? "#EEEEEE !important" : "#ffffff", + } + })); + +const BpIcon = styled('span')(() => ({ +borderRadius: '50%', +width: 24, +height: 24, +outline: 'solid 2px #1D91AB', +'input:hover:enabled ~ &': { + outlineOffset: "2px", +}, +})); + +const BpCheckedIcon = styled(BpIcon)({ +'&:before': { + borderRadius: "50%", + display: 'block', + marginTop: "4px", + marginLeft: "4px", + width: 16, + height: 16, + backgroundColor: '#1D91AB', + content: '""', +}, +}); + +const StyledRadioButton = (props: RadioProps) => ( + } + icon={} + inputProps={{ + "data-type": "auto" + } as unknown} + {...props} + /> +); + +export default StyledRadioButton; diff --git a/src/content/dataSubmissions/DataSubmission.tsx b/src/content/dataSubmissions/DataSubmission.tsx index 1dee613b..902171aa 100644 --- a/src/content/dataSubmissions/DataSubmission.tsx +++ b/src/content/dataSubmissions/DataSubmission.tsx @@ -10,11 +10,16 @@ import { CardActionsProps, CardContent, Container, + FormControlLabel, + RadioGroup, Stack, Tabs, styled, } from "@mui/material"; +import { LoadingButton } from "@mui/lab"; + import { isEqual } from "lodash"; +import { useAuthContext } from '../../components/Contexts/AuthContext'; import bannerSvg from "../../assets/dataSubmissions/dashboard_banner.svg"; import summaryBannerSvg from "../../assets/dataSubmissions/summary_banner.png"; import LinkTab from "../../components/DataSubmissions/LinkTab"; @@ -33,6 +38,32 @@ import DataSubmissionBatchTable, { Column, FetchListing, TableMethods } from ".. import { FormatDate } from "../../utils"; import DataSubmissionActions from "./DataSubmissionActions"; import QualityControl from "./QualityControl"; +import StyledRadioButton from "../../components/Questionnaire/StyledRadioButton"; + +const StyledValidateButton = styled(LoadingButton)(() => ({ + alignSelf: "center", + display: "flex", + flexDirection: "column", + padding: "12px 22px", + justifyContent: "center", + alignItems: "center", + borderRadius: "8px", + background: "#005EA2", + color: "#FFF", + textAlign: "center", + fontFamily: "'Nunito'", + fontSize: "16px", + fontStyle: "normal", + fontWeight: 700, + lineHeight: "16px", + letterSpacing: "0.32px", + textTransform: "none", + "&.MuiButtonBase-root": { + height: "fit-content", + marginLeft: "auto", + marginRight: "21.5px" + } +})); const StyledBanner = styled("div")(({ bannerSrc }: { bannerSrc: string }) => ({ background: `url(${bannerSrc})`, @@ -94,6 +125,57 @@ const StyledCard = styled(Card)(() => ({ }, })); +const StyledFileValidationSection = styled("div")(() => ({ + borderRadius: 0, + minHeight: "125px", + padding: "21px 40px 0", + borderTop: "solid 1.5px #6CACDA", + background: "#E3EEF9", + gridAutoFlow: "row", + gridTemplateColumns: "2.5fr 0.5fr", + display: "grid", + ".headerText": { + fontFamily: "Nunito", + fontSize: "16px", + fontWeight: "700", + lineHeight: "20px", + letterSpacing: "0em", + textAlign: "left", + minWidth: "200px;" + }, + + ".fileValidationLeftSide": { + display: "flex", + flexDirection: "column", + }, + ".fileValidationLeftSideTopRow": { + display: "grid", + gridTemplateColumns: "1fr 3fr", + height: "45px", + alignItems: "center", + borderBottom: "1px solid #0B7F99", + width: "710px", + }, + ".fileValidationLeftSideBottomRow": { + display: "grid", + gridTemplateColumns: "1fr 3fr", + height: "45px", + alignItems: "center", + width: "800px", + }, + ".fileValidationRadioButtonGroup": { + marginLeft: "20px", + }, + "& .MuiFormControlLabel-label": { + fontFamily: "Nunito", + fontSize: "16px", + fontWeight: "500", + lineHeight: "20px", + letterSpacing: "0em", + textAlign: "left", + } +})); + const StyledMainContentArea = styled("div")(() => ({ borderRadius: 0, background: "#FFFFFF", @@ -108,6 +190,7 @@ const StyledCardActions = styled(CardActions, { })); const StyledTabs = styled(Tabs)(() => ({ + background: "#E3EEF9", position: 'relative', "& .MuiTabs-flexContainer": { justifyContent: "center" @@ -188,10 +271,11 @@ const URLTabs = { }; const submissionLockedStatuses: SubmissionStatus[] = ["Submitted", "Released", "Completed", "Canceled", "Archived"]; - +type ValidationType = "Metadata" | "Data Files" | "Both"; +type UploadType = "New" | "All"; const DataSubmission = () => { const { submissionId, tab } = useParams(); - + const { user } = useAuthContext(); const [dataSubmission, setDataSubmission] = useState(null); const [batchFiles, setBatchFiles] = useState([]); const [totalBatchFiles, setTotalBatchFiles] = useState(0); @@ -201,6 +285,10 @@ const DataSubmission = () => { const [changesAlert, setChangesAlert] = useState(null); const tableRef = useRef(null); const isValidTab = tab && Object.values(URLTabs).includes(tab); + const [validationType, setValidationType] = useState(null); + const [uploadType, setUploadType] = useState(null); + + const canValidateData = (user?.role === "Submitter" || user?.role === "Data Curator" || user?.role === "Organization Owner" || user?.role === "Admin"); const [getSubmission] = useLazyQuery(GET_SUBMISSION, { variables: { id: submissionId }, @@ -340,7 +428,82 @@ const DataSubmission = () => { {/* TODO: Widgets removed for MVP2-M2. Will be re-added in the future */} - + +
+
+
Choose validation type:
+
+ setValidationType(val)} + > + } + label="Validate Metadata" + disabled={!canValidateData} + /> + } label="Validate data files" + disabled={!canValidateData} + + /> + } label="Both" + disabled={!canValidateData} + /> + +
+
+
+
Choose upload type:
+
+ setUploadType(val)} + > + } + label="New uploaded data" + disabled={!canValidateData || validationType === null} + + /> + } + label="All uploaded available data" + disabled={!canValidateData || validationType === null} + /> + +
+
+
+ + Validate + +
Date: Thu, 16 Nov 2023 11:51:43 -0500 Subject: [PATCH 02/11] styling buttons --- .../DataSubmissions/DataSubmissionUpload.tsx | 7 ++++--- src/content/dataSubmissions/DataSubmission.tsx | 17 +++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/DataSubmissions/DataSubmissionUpload.tsx b/src/components/DataSubmissions/DataSubmissionUpload.tsx index 12e63377..5087bed5 100644 --- a/src/components/DataSubmissions/DataSubmissionUpload.tsx +++ b/src/components/DataSubmissions/DataSubmissionUpload.tsx @@ -30,7 +30,7 @@ const StyledMetadataText = styled(StyledUploadTypeText)(() => ({ const StyledUploadFilesButton = styled(LoadingButton)(() => ({ display: "flex", flexDirection: "column", - padding: "12px 22px", + padding: "12px 20px", justifyContent: "center", alignItems: "center", borderRadius: "8px", @@ -46,7 +46,8 @@ const StyledUploadFilesButton = styled(LoadingButton)(() => ({ textTransform: "none", "&.MuiButtonBase-root": { marginLeft: "auto", - marginRight: "21.5px" + marginRight: "21.5px", + minWidth: "137px", } })); @@ -336,7 +337,7 @@ const DataSubmissionUpload = ({ submitterID, readOnly, onUpload }: Props) => { disableRipple disableTouchRipple > - Upload Files + Upload ); diff --git a/src/content/dataSubmissions/DataSubmission.tsx b/src/content/dataSubmissions/DataSubmission.tsx index 902171aa..93cfce3b 100644 --- a/src/content/dataSubmissions/DataSubmission.tsx +++ b/src/content/dataSubmissions/DataSubmission.tsx @@ -44,7 +44,7 @@ const StyledValidateButton = styled(LoadingButton)(() => ({ alignSelf: "center", display: "flex", flexDirection: "column", - padding: "12px 22px", + padding: "12px 20px", justifyContent: "center", alignItems: "center", borderRadius: "8px", @@ -61,7 +61,8 @@ const StyledValidateButton = styled(LoadingButton)(() => ({ "&.MuiButtonBase-root": { height: "fit-content", marginLeft: "auto", - marginRight: "21.5px" + marginRight: "21.5px", + minWidth: "137px", } })); @@ -285,11 +286,11 @@ const DataSubmission = () => { const [changesAlert, setChangesAlert] = useState(null); const tableRef = useRef(null); const isValidTab = tab && Object.values(URLTabs).includes(tab); - const [validationType, setValidationType] = useState(null); - const [uploadType, setUploadType] = useState(null); + const [validationType, setValidationType] = useState("Metadata"); + const [uploadType, setUploadType] = useState("New"); const canValidateData = (user?.role === "Submitter" || user?.role === "Data Curator" || user?.role === "Organization Owner" || user?.role === "Admin"); - + const validateButtonEnabled = dataSubmission?.status === "In Progress" || dataSubmission?.status === "Withdrawn" || dataSubmission?.status === "Rejected"; const [getSubmission] = useLazyQuery(GET_SUBMISSION, { variables: { id: submissionId }, context: { clientName: 'backend' }, @@ -478,7 +479,7 @@ const DataSubmission = () => { color="#1D91AB" control={} label="New uploaded data" - disabled={!canValidateData || validationType === null} + disabled={!canValidateData} /> { color="#1D91AB" control={} label="All uploaded available data" - disabled={!canValidateData || validationType === null} + disabled={!canValidateData} /> @@ -499,7 +500,7 @@ const DataSubmission = () => { disableElevation disableRipple disableTouchRipple - disabled={!canValidateData || validationType === null || uploadType === null} + disabled={!canValidateData || !validateButtonEnabled} > Validate From 77936560bb3a86093e8969a0649784ec9e64fc26 Mon Sep 17 00:00:00 2001 From: Michael Tang Date: Mon, 20 Nov 2023 11:11:35 -0500 Subject: [PATCH 03/11] add temp validate handler --- src/content/dataSubmissions/DataSubmission.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/content/dataSubmissions/DataSubmission.tsx b/src/content/dataSubmissions/DataSubmission.tsx index 4b2968ef..4086652b 100644 --- a/src/content/dataSubmissions/DataSubmission.tsx +++ b/src/content/dataSubmissions/DataSubmission.tsx @@ -51,7 +51,7 @@ const StyledValidateButton = styled(LoadingButton)(() => ({ justifyContent: "center", alignItems: "center", borderRadius: "8px", - background: "#005EA2", + background: "#1A7B90", color: "#FFF", textAlign: "center", fontFamily: "'Nunito'", @@ -61,11 +61,19 @@ const StyledValidateButton = styled(LoadingButton)(() => ({ lineHeight: "16px", letterSpacing: "0.32px", textTransform: "none", + border: "1.5px solid #136071", "&.MuiButtonBase-root": { height: "fit-content", marginLeft: "auto", marginRight: "21.5px", minWidth: "137px", + }, + "&.MuiButtonBase-root:hover": { + background: "#496065", + height: "fit-content", + marginLeft: "auto", + marginRight: "21.5px", + minWidth: "137px", } })); @@ -454,6 +462,11 @@ const DataSubmission = () => { } }; + const handleValidateFiles = async () => { + // TODO: Call the Validate Submission API with the correct params: + console.log("Validating Files with these params: ", validationType, uploadType); + }; + const handleCopyID = () => { if (!submissionId) { return; @@ -563,6 +576,7 @@ const DataSubmission = () => { disableRipple disableTouchRipple disabled={!canValidateData || !validateButtonEnabled} + onClick={handleValidateFiles} > Validate From f83a80888c077be6ac6ff14959c9587c2c187b7d Mon Sep 17 00:00:00 2001 From: Michael Tang Date: Mon, 20 Nov 2023 15:50:42 -0500 Subject: [PATCH 04/11] update validate button styling --- src/content/dataSubmissions/DataSubmission.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/content/dataSubmissions/DataSubmission.tsx b/src/content/dataSubmissions/DataSubmission.tsx index 4086652b..6348079d 100644 --- a/src/content/dataSubmissions/DataSubmission.tsx +++ b/src/content/dataSubmissions/DataSubmission.tsx @@ -68,6 +68,14 @@ const StyledValidateButton = styled(LoadingButton)(() => ({ marginRight: "21.5px", minWidth: "137px", }, + "&.MuiButtonBase-root:disabled": { + height: "fit-content", + marginLeft: "auto", + marginRight: "21.5px", + minWidth: "137px", + background: "#949494", + color: "#CBCBCB", + }, "&.MuiButtonBase-root:hover": { background: "#496065", height: "fit-content", From 7cfae0b8c1c0b3d2d0ad8f5faa12a9edd1cde1ee Mon Sep 17 00:00:00 2001 From: Alec M Date: Wed, 29 Nov 2023 17:05:19 -0500 Subject: [PATCH 05/11] CRDCDH-539 Migrate to independent component This will help reduce merge conflicts --- .../DataSubmissions/ValidationControls.tsx | 243 ++++++++++++++++++ .../dataSubmissions/DataSubmission.tsx | 192 +------------- src/graphql/index.ts | 3 + src/graphql/validateSubmission.ts | 11 + 4 files changed, 261 insertions(+), 188 deletions(-) create mode 100644 src/components/DataSubmissions/ValidationControls.tsx create mode 100644 src/graphql/validateSubmission.ts diff --git a/src/components/DataSubmissions/ValidationControls.tsx b/src/components/DataSubmissions/ValidationControls.tsx new file mode 100644 index 00000000..7c303c03 --- /dev/null +++ b/src/components/DataSubmissions/ValidationControls.tsx @@ -0,0 +1,243 @@ +import { FC, useMemo, useState } from 'react'; +import { useMutation } from '@apollo/client'; +import { FormControlLabel, RadioGroup, styled } from '@mui/material'; +import { LoadingButton } from '@mui/lab'; +import { useAuthContext } from '../Contexts/AuthContext'; +import StyledRadioButton from "../Questionnaire/StyledRadioButton"; +import { VALIDATE_SUBMISSION, ValidateSubmissionResp } from '../../graphql'; + +type Props = { + /** + * The data submission to display validation controls for. + * + * NOTE: Initially null during the loading state. + */ + dataSubmission?: Submission; +}; + +type ValidationType = "Metadata" | "Files" | "All"; +type UploadType = "New" | "All"; + +const StyledValidateButton = styled(LoadingButton)({ + alignSelf: "center", + display: "flex", + flexDirection: "column", + padding: "12px 20px", + justifyContent: "center", + alignItems: "center", + borderRadius: "8px", + background: "#1A7B90", + color: "#FFF", + textAlign: "center", + fontFamily: "'Nunito'", + fontSize: "16px", + fontStyle: "normal", + fontWeight: 700, + lineHeight: "16px", + letterSpacing: "0.32px", + textTransform: "none", + border: "1.5px solid #136071", + "&.MuiButtonBase-root": { + height: "fit-content", + marginLeft: "auto", + marginRight: "21.5px", + minWidth: "137px", + }, + "&.MuiButtonBase-root:disabled": { + height: "fit-content", + marginLeft: "auto", + marginRight: "21.5px", + minWidth: "137px", + background: "#949494", + color: "#CBCBCB", + }, + "&.MuiButtonBase-root:hover": { + background: "#496065", + height: "fit-content", + marginLeft: "auto", + marginRight: "21.5px", + minWidth: "137px", + } +}); + +const StyledFileValidationSection = styled("div")({ + borderRadius: 0, + minHeight: "125px", + padding: "21px 40px 0", + borderTop: "solid 1.5px #6CACDA", + background: "#F0FBFD", + gridAutoFlow: "row", + gridTemplateColumns: "2.5fr 0.5fr", + display: "grid", + ".headerText": { + fontFamily: "Nunito", + fontSize: "16px", + fontWeight: "700", + lineHeight: "20px", + letterSpacing: "0em", + textAlign: "left", + minWidth: "200px;" + }, + ".fileValidationLeftSide": { + display: "flex", + flexDirection: "column", + }, + ".fileValidationLeftSideTopRow": { + display: "grid", + gridTemplateColumns: "1fr 3fr", + height: "45px", + alignItems: "center", + borderBottom: "1px solid #0B7F99", + width: "710px", + }, + ".fileValidationLeftSideBottomRow": { + display: "grid", + gridTemplateColumns: "1fr 3fr", + height: "45px", + alignItems: "center", + width: "800px", + }, + ".fileValidationRadioButtonGroup": { + marginLeft: "20px", + }, + "& .MuiFormControlLabel-label": { + fontFamily: "Nunito", + fontSize: "16px", + fontWeight: "500", + lineHeight: "20px", + letterSpacing: "0em", + textAlign: "left", + } +}); + +const ValidateRoles: User["role"][] = ["Submitter", "Data Curator", "Organization Owner", "Admin"]; +const ValidateStatuses: Submission["status"][] = ["In Progress", "Withdrawn", "Rejected"]; + +/** + * Provides the UI for validating a data submission's assets. + * + * @param {Props} + * @returns {React.FC} + */ +const ValidationControls: FC = ({ dataSubmission }: Props) => { + const { user } = useAuthContext(); + const [validationType, setValidationType] = useState("Metadata"); + const [uploadType, setUploadType] = useState("New"); + const [isValidating, setIsValidating] = useState(false); + + const canValidateData: boolean = useMemo(() => ValidateRoles.includes(user?.role), [user?.role]); + const validateButtonEnabled: boolean = useMemo(() => ValidateStatuses.includes(dataSubmission?.status), [dataSubmission?.status]); + + const [validateSubmission] = useMutation(VALIDATE_SUBMISSION, { + context: { clientName: 'backend' }, + fetchPolicy: 'no-cache' + }); + + const handleValidateFiles = async () => { + setIsValidating(true); + + // TODO: Remove + // eslint-disable-next-line no-console + console.log("Validating Files with these params: ", validationType, uploadType); + + const d = await validateSubmission({ + variables: { + _id: dataSubmission?._id, + types: getTypes(validationType), + scope: uploadType === "New" ? "New" : "All", + } + }); + + // TODO: Remove + // eslint-disable-next-line no-console + console.log(d); + + // Reset form to default values + setValidationType("Metadata"); + setUploadType("New"); + setIsValidating(false); + }; + + const getTypes = (validationType: ValidationType): string[] => { + switch (validationType) { + case "Metadata": + return ["metadata"]; + case "Files": + return ["file"]; + default: + return ["metadata", "file"]; + } + }; + + return ( + +
+
+
Choose validation type:
+
+ setValidationType(val)} row> + } + label="Validate Metadata" + disabled={!canValidateData} + /> + } + label="Validate Data Files" + disabled={!canValidateData} + /> + } + label="Both" + disabled={!canValidateData} + /> + +
+
+
+
Choose validation target:
+
+ setUploadType(val)} row> + } + label="New Uploaded Data" + disabled={!canValidateData} + /> + } + label="All Uploaded Data" + disabled={!canValidateData} + /> + +
+
+
+ + Validate + +
+ ); +}; + +export default ValidationControls; diff --git a/src/content/dataSubmissions/DataSubmission.tsx b/src/content/dataSubmissions/DataSubmission.tsx index 6348079d..6f8a5327 100644 --- a/src/content/dataSubmissions/DataSubmission.tsx +++ b/src/content/dataSubmissions/DataSubmission.tsx @@ -10,18 +10,14 @@ import { CardActionsProps, CardContent, Container, - FormControlLabel, - RadioGroup, IconButton, Stack, Tabs, Typography, styled, } from "@mui/material"; -import { LoadingButton } from "@mui/lab"; import { isEqual } from "lodash"; -import { useAuthContext } from '../../components/Contexts/AuthContext'; import bannerSvg from "../../assets/dataSubmissions/dashboard_banner.svg"; import summaryBannerSvg from "../../assets/dataSubmissions/summary_banner.png"; import LinkTab from "../../components/DataSubmissions/LinkTab"; @@ -40,50 +36,8 @@ import DataSubmissionBatchTable, { Column, FetchListing, TableMethods } from ".. import { FormatDate } from "../../utils"; import DataSubmissionActions from "./DataSubmissionActions"; import QualityControl from "./QualityControl"; -import StyledRadioButton from "../../components/Questionnaire/StyledRadioButton"; import { ReactComponent as CopyIconSvg } from "../../assets/icons/copy_icon_2.svg"; - -const StyledValidateButton = styled(LoadingButton)(() => ({ - alignSelf: "center", - display: "flex", - flexDirection: "column", - padding: "12px 20px", - justifyContent: "center", - alignItems: "center", - borderRadius: "8px", - background: "#1A7B90", - color: "#FFF", - textAlign: "center", - fontFamily: "'Nunito'", - fontSize: "16px", - fontStyle: "normal", - fontWeight: 700, - lineHeight: "16px", - letterSpacing: "0.32px", - textTransform: "none", - border: "1.5px solid #136071", - "&.MuiButtonBase-root": { - height: "fit-content", - marginLeft: "auto", - marginRight: "21.5px", - minWidth: "137px", - }, - "&.MuiButtonBase-root:disabled": { - height: "fit-content", - marginLeft: "auto", - marginRight: "21.5px", - minWidth: "137px", - background: "#949494", - color: "#CBCBCB", - }, - "&.MuiButtonBase-root:hover": { - background: "#496065", - height: "fit-content", - marginLeft: "auto", - marginRight: "21.5px", - minWidth: "137px", - } -})); +import ValidationControls from '../../components/DataSubmissions/ValidationControls'; const StyledBanner = styled("div")(({ bannerSrc }: { bannerSrc: string }) => ({ background: `url(${bannerSrc})`, @@ -147,57 +101,6 @@ const StyledCard = styled(Card)(() => ({ }, })); -const StyledFileValidationSection = styled("div")(() => ({ - borderRadius: 0, - minHeight: "125px", - padding: "21px 40px 0", - borderTop: "solid 1.5px #6CACDA", - background: "#E3EEF9", - gridAutoFlow: "row", - gridTemplateColumns: "2.5fr 0.5fr", - display: "grid", - ".headerText": { - fontFamily: "Nunito", - fontSize: "16px", - fontWeight: "700", - lineHeight: "20px", - letterSpacing: "0em", - textAlign: "left", - minWidth: "200px;" - }, - - ".fileValidationLeftSide": { - display: "flex", - flexDirection: "column", - }, - ".fileValidationLeftSideTopRow": { - display: "grid", - gridTemplateColumns: "1fr 3fr", - height: "45px", - alignItems: "center", - borderBottom: "1px solid #0B7F99", - width: "710px", - }, - ".fileValidationLeftSideBottomRow": { - display: "grid", - gridTemplateColumns: "1fr 3fr", - height: "45px", - alignItems: "center", - width: "800px", - }, - ".fileValidationRadioButtonGroup": { - marginLeft: "20px", - }, - "& .MuiFormControlLabel-label": { - fontFamily: "Nunito", - fontSize: "16px", - fontWeight: "500", - lineHeight: "20px", - letterSpacing: "0em", - textAlign: "left", - } -})); - const StyledMainContentArea = styled("div")(() => ({ borderRadius: 0, background: "#FFFFFF", @@ -212,7 +115,7 @@ const StyledCardActions = styled(CardActions, { })); const StyledTabs = styled(Tabs)(() => ({ - background: "#E3EEF9", + background: "#F0FBFD", position: 'relative', "& .MuiTabs-flexContainer": { justifyContent: "center" @@ -334,11 +237,9 @@ const URLTabs = { }; const submissionLockedStatuses: SubmissionStatus[] = ["Submitted", "Released", "Completed", "Canceled", "Archived"]; -type ValidationType = "Metadata" | "Data Files" | "Both"; -type UploadType = "New" | "All"; + const DataSubmission = () => { const { submissionId, tab } = useParams(); - const { user } = useAuthContext(); const [dataSubmission, setDataSubmission] = useState(null); const [batchFiles, setBatchFiles] = useState([]); const [totalBatchFiles, setTotalBatchFiles] = useState(0); @@ -348,11 +249,7 @@ const DataSubmission = () => { const [changesAlert, setChangesAlert] = useState(null); const tableRef = useRef(null); const isValidTab = tab && Object.values(URLTabs).includes(tab); - const [validationType, setValidationType] = useState("Metadata"); - const [uploadType, setUploadType] = useState("New"); - const canValidateData = (user?.role === "Submitter" || user?.role === "Data Curator" || user?.role === "Organization Owner" || user?.role === "Admin"); - const validateButtonEnabled = dataSubmission?.status === "In Progress" || dataSubmission?.status === "Withdrawn" || dataSubmission?.status === "Rejected"; const [getSubmission] = useLazyQuery(GET_SUBMISSION, { variables: { id: submissionId }, context: { clientName: 'backend' }, @@ -470,11 +367,6 @@ const DataSubmission = () => { } }; - const handleValidateFiles = async () => { - // TODO: Call the Validate Submission API with the correct params: - console.log("Validating Files with these params: ", validationType, uploadType); - }; - const handleCopyID = () => { if (!submissionId) { return; @@ -512,83 +404,7 @@ const DataSubmission = () => { {/* TODO: Widgets removed for MVP2-M2. Will be re-added in the future */} - -
-
-
Choose validation type:
-
- setValidationType(val)} - > - } - label="Validate Metadata" - disabled={!canValidateData} - /> - } label="Validate data files" - disabled={!canValidateData} - - /> - } label="Both" - disabled={!canValidateData} - /> - -
-
-
-
Choose upload type:
-
- setUploadType(val)} - > - } - label="New uploaded data" - disabled={!canValidateData} - - /> - } - label="All uploaded available data" - disabled={!canValidateData} - /> - -
-
-
- - Validate - -
+ Date: Wed, 29 Nov 2023 17:17:07 -0500 Subject: [PATCH 06/11] CRDCDH-539 Fine tune spacing and colors --- src/components/DataSubmissions/ValidationControls.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/DataSubmissions/ValidationControls.tsx b/src/components/DataSubmissions/ValidationControls.tsx index 7c303c03..32135d6a 100644 --- a/src/components/DataSubmissions/ValidationControls.tsx +++ b/src/components/DataSubmissions/ValidationControls.tsx @@ -62,7 +62,7 @@ const StyledValidateButton = styled(LoadingButton)({ const StyledFileValidationSection = styled("div")({ borderRadius: 0, - minHeight: "125px", + minHeight: "147px", padding: "21px 40px 0", borderTop: "solid 1.5px #6CACDA", background: "#F0FBFD", @@ -71,12 +71,13 @@ const StyledFileValidationSection = styled("div")({ display: "grid", ".headerText": { fontFamily: "Nunito", + color: "#083A50", fontSize: "16px", fontWeight: "700", lineHeight: "20px", letterSpacing: "0em", textAlign: "left", - minWidth: "200px;" + minWidth: "270px" }, ".fileValidationLeftSide": { display: "flex", @@ -85,15 +86,15 @@ const StyledFileValidationSection = styled("div")({ ".fileValidationLeftSideTopRow": { display: "grid", gridTemplateColumns: "1fr 3fr", - height: "45px", + height: "50px", alignItems: "center", borderBottom: "1px solid #0B7F99", - width: "710px", + width: "800px", }, ".fileValidationLeftSideBottomRow": { display: "grid", gridTemplateColumns: "1fr 3fr", - height: "45px", + height: "50px", alignItems: "center", width: "800px", }, From f8914de71b213d3093cc2e5fdfecc6cbfbd531a7 Mon Sep 17 00:00:00 2001 From: Alec M Date: Tue, 5 Dec 2023 09:28:04 -0500 Subject: [PATCH 07/11] CRDCDH-539 Address TODOs --- .../DataSubmissions/ValidationControls.tsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/components/DataSubmissions/ValidationControls.tsx b/src/components/DataSubmissions/ValidationControls.tsx index 32135d6a..7550be24 100644 --- a/src/components/DataSubmissions/ValidationControls.tsx +++ b/src/components/DataSubmissions/ValidationControls.tsx @@ -137,11 +137,7 @@ const ValidationControls: FC = ({ dataSubmission }: Props) => { const handleValidateFiles = async () => { setIsValidating(true); - // TODO: Remove - // eslint-disable-next-line no-console - console.log("Validating Files with these params: ", validationType, uploadType); - - const d = await validateSubmission({ + await validateSubmission({ variables: { _id: dataSubmission?._id, types: getTypes(validationType), @@ -149,10 +145,6 @@ const ValidationControls: FC = ({ dataSubmission }: Props) => { } }); - // TODO: Remove - // eslint-disable-next-line no-console - console.log(d); - // Reset form to default values setValidationType("Metadata"); setUploadType("New"); @@ -174,7 +166,7 @@ const ValidationControls: FC = ({ dataSubmission }: Props) => {
-
Choose validation type:
+
Validation Type:
setValidationType(val)} row> = ({ dataSubmission }: Props) => {
-
Choose validation target:
+
Validation Target:
setUploadType(val)} row> Date: Wed, 6 Dec 2023 14:01:54 -0500 Subject: [PATCH 08/11] Fine tune UI styling --- .../DataSubmissions/ValidationControls.tsx | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/components/DataSubmissions/ValidationControls.tsx b/src/components/DataSubmissions/ValidationControls.tsx index 7550be24..c9638dd4 100644 --- a/src/components/DataSubmissions/ValidationControls.tsx +++ b/src/components/DataSubmissions/ValidationControls.tsx @@ -101,14 +101,22 @@ const StyledFileValidationSection = styled("div")({ ".fileValidationRadioButtonGroup": { marginLeft: "20px", }, - "& .MuiFormControlLabel-label": { - fontFamily: "Nunito", - fontSize: "16px", - fontWeight: "500", - lineHeight: "20px", - letterSpacing: "0em", - textAlign: "left", - } +}); + +const StyledRadioControl = styled(FormControlLabel)({ + fontFamily: "Nunito", + fontSize: "16px", + fontWeight: "500", + lineHeight: "20px", + letterSpacing: "0em", + textAlign: "left", + color: "#083A50", + minWidth: "200px", + marginRight: "20px", + "&:last-child": { + marginRight: "0px", + minWidth: "unset", + }, }); const ValidateRoles: User["role"][] = ["Submitter", "Data Curator", "Organization Owner", "Admin"]; @@ -169,26 +177,20 @@ const ValidationControls: FC = ({ dataSubmission }: Props) => {
Validation Type:
setValidationType(val)} row> - } label="Validate Metadata" disabled={!canValidateData} /> - } label="Validate Data Files" disabled={!canValidateData} /> - } label="Both" disabled={!canValidateData} @@ -200,18 +202,14 @@ const ValidationControls: FC = ({ dataSubmission }: Props) => {
Validation Target:
setUploadType(val)} row> - } label="New Uploaded Data" disabled={!canValidateData} /> - } label="All Uploaded Data" disabled={!canValidateData} From c6e290d567648542ef959a33eb959a21c2c16226 Mon Sep 17 00:00:00 2001 From: Alec M Date: Thu, 7 Dec 2023 15:09:54 -0500 Subject: [PATCH 09/11] Add validation status alert --- .../DataSubmissions/ValidationControls.tsx | 14 +++++++++++++- src/graphql/validateSubmission.ts | 6 ++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/components/DataSubmissions/ValidationControls.tsx b/src/components/DataSubmissions/ValidationControls.tsx index c9638dd4..e76b4ab6 100644 --- a/src/components/DataSubmissions/ValidationControls.tsx +++ b/src/components/DataSubmissions/ValidationControls.tsx @@ -5,6 +5,7 @@ import { LoadingButton } from '@mui/lab'; import { useAuthContext } from '../Contexts/AuthContext'; import StyledRadioButton from "../Questionnaire/StyledRadioButton"; import { VALIDATE_SUBMISSION, ValidateSubmissionResp } from '../../graphql'; +import GenericAlert, { AlertState } from '../GenericAlert'; type Props = { /** @@ -133,6 +134,7 @@ const ValidationControls: FC = ({ dataSubmission }: Props) => { const [validationType, setValidationType] = useState("Metadata"); const [uploadType, setUploadType] = useState("New"); const [isValidating, setIsValidating] = useState(false); + const [validationAlert, setValidationAlert] = useState(null); const canValidateData: boolean = useMemo(() => ValidateRoles.includes(user?.role), [user?.role]); const validateButtonEnabled: boolean = useMemo(() => ValidateStatuses.includes(dataSubmission?.status), [dataSubmission?.status]); @@ -145,7 +147,7 @@ const ValidationControls: FC = ({ dataSubmission }: Props) => { const handleValidateFiles = async () => { setIsValidating(true); - await validateSubmission({ + const { data, errors } = await validateSubmission({ variables: { _id: dataSubmission?._id, types: getTypes(validationType), @@ -153,10 +155,17 @@ const ValidationControls: FC = ({ dataSubmission }: Props) => { } }); + if (errors || !data?.validateSubmission || data.validateSubmission === "false") { + setValidationAlert({ message: "TODO: Failed", severity: "error" }); + } else { + setValidationAlert({ message: "TODO: Success", severity: "success" }); + } + // Reset form to default values setValidationType("Metadata"); setUploadType("New"); setIsValidating(false); + setTimeout(() => setValidationAlert(null), 10000); }; const getTypes = (validationType: ValidationType): string[] => { @@ -172,6 +181,9 @@ const ValidationControls: FC = ({ dataSubmission }: Props) => { return ( + + {validationAlert?.message} +
Validation Type:
diff --git a/src/graphql/validateSubmission.ts b/src/graphql/validateSubmission.ts index 198d0eaf..a4cd514d 100644 --- a/src/graphql/validateSubmission.ts +++ b/src/graphql/validateSubmission.ts @@ -7,5 +7,11 @@ export const mutation = gql` `; export type Response = { + /** + * The boolean result of the validation + * + * If the validation is successful, the result will be "true" + * If the validation is unsuccessful, the result will be "false" + */ validateSubmission: string; }; From 7c232e90fdac1df12b9fae487ca30ba34ff41eef Mon Sep 17 00:00:00 2001 From: Alec M Date: Fri, 8 Dec 2023 10:17:20 -0500 Subject: [PATCH 10/11] Add success message and disable button after validation start --- .../DataSubmissions/ValidationControls.tsx | 20 +++++++++++-------- src/graphql/validateSubmission.ts | 12 ++++------- src/types/Submissions.d.ts | 11 ++++++++++ 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/components/DataSubmissions/ValidationControls.tsx b/src/components/DataSubmissions/ValidationControls.tsx index e76b4ab6..cb3fa921 100644 --- a/src/components/DataSubmissions/ValidationControls.tsx +++ b/src/components/DataSubmissions/ValidationControls.tsx @@ -17,6 +17,7 @@ type Props = { }; type ValidationType = "Metadata" | "Files" | "All"; + type UploadType = "New" | "All"; const StyledValidateButton = styled(LoadingButton)({ @@ -133,6 +134,7 @@ const ValidationControls: FC = ({ dataSubmission }: Props) => { const { user } = useAuthContext(); const [validationType, setValidationType] = useState("Metadata"); const [uploadType, setUploadType] = useState("New"); + const [isLoading, setIsLoading] = useState(false); const [isValidating, setIsValidating] = useState(false); const [validationAlert, setValidationAlert] = useState(null); @@ -145,7 +147,7 @@ const ValidationControls: FC = ({ dataSubmission }: Props) => { }); const handleValidateFiles = async () => { - setIsValidating(true); + setIsLoading(true); const { data, errors } = await validateSubmission({ variables: { @@ -155,16 +157,18 @@ const ValidationControls: FC = ({ dataSubmission }: Props) => { } }); - if (errors || !data?.validateSubmission || data.validateSubmission === "false") { - setValidationAlert({ message: "TODO: Failed", severity: "error" }); + if (errors || !data?.validateSubmission?.success) { + setValidationAlert({ message: "Unable to initiate validation process.", severity: "error" }); + setIsValidating(false); } else { - setValidationAlert({ message: "TODO: Success", severity: "success" }); + setValidationAlert({ message: "Validation process is starting; this may take some time. Please wait before initiating another validation.", severity: "success" }); + setIsValidating(true); } // Reset form to default values setValidationType("Metadata"); setUploadType("New"); - setIsValidating(false); + setIsLoading(false); setTimeout(() => setValidationAlert(null), 10000); }; @@ -233,11 +237,11 @@ const ValidationControls: FC = ({ dataSubmission }: Props) => { - Validate + {isValidating ? "Validating..." : "Validate"} ); diff --git a/src/graphql/validateSubmission.ts b/src/graphql/validateSubmission.ts index a4cd514d..b4ba48b2 100644 --- a/src/graphql/validateSubmission.ts +++ b/src/graphql/validateSubmission.ts @@ -2,16 +2,12 @@ import gql from 'graphql-tag'; export const mutation = gql` mutation validateSubmission($_id: ID!, $types: [String], $scope: String) { - validateSubmission(_id: $_id, types: $types, scope: $scope) + validateSubmission(_id: $_id, types: $types, scope: $scope) { + success + } } `; export type Response = { - /** - * The boolean result of the validation - * - * If the validation is successful, the result will be "true" - * If the validation is unsuccessful, the result will be "false" - */ - validateSubmission: string; + validateSubmission: Pick; }; diff --git a/src/types/Submissions.d.ts b/src/types/Submissions.d.ts index c9d08396..525fc188 100644 --- a/src/types/Submissions.d.ts +++ b/src/types/Submissions.d.ts @@ -191,3 +191,14 @@ type DataRecord = { s3FileInfo: S3FileInfo; // only for "file" types, should be null for other nodes CRDC_ID: string; }; + +type DataValidationResult = { + /** + * Whether the validation action was successfully queued. + */ + success: boolean; + /** + * The message returned by the validation. + */ + message: string; +}; From fc1ad7956f9fd6a648876149e2d36403f412cdb4 Mon Sep 17 00:00:00 2001 From: Alec M Date: Mon, 11 Dec 2023 09:37:49 -0500 Subject: [PATCH 11/11] Revert accidental reversion of styling update --- src/components/DataSubmissions/RadioInput.tsx | 65 ++++++++++--------- .../Questionnaire/RadioYesNoInput.tsx | 64 +++++++++--------- 2 files changed, 67 insertions(+), 62 deletions(-) diff --git a/src/components/DataSubmissions/RadioInput.tsx b/src/components/DataSubmissions/RadioInput.tsx index 83f48254..80b8dcaa 100644 --- a/src/components/DataSubmissions/RadioInput.tsx +++ b/src/components/DataSubmissions/RadioInput.tsx @@ -8,40 +8,43 @@ import { FormHelperText, Stack, styled, + GridProps, } from "@mui/material"; import { updateInputValidity } from "../../utils"; import StyledRadioButton from '../Questionnaire/StyledRadioButton'; -const GridStyled = styled(Grid)<{ $containerWidth?: string; }>` - width: ${(props) => props.$containerWidth}; - .formControl{ - margin-top: 8px; - margin-bottom: 4px; - } - .css-hsm3ra-MuiFormLabel-root { - color: rgba(0, 0, 0, 0.6) !important; - } - .MuiRadio-root { - color: #1D91AB !important; - margin-left: 10px; - padding-right: 7px; - } - #invisibleRadioInput { - height: 0; - border: none; - width: 0; - } - .MuiFormHelperText-root { - color: #083A50; - margin-left: 0; - } - .MuiFormHelperText-root.Mui-error { - color: #D54309 !important; - } - .displayNone { - display: none !important; - } -`; +const GridStyled = styled(Grid, { + shouldForwardProp: (prop) => prop !== "containerWidth", +})(({ containerWidth }) => ({ + width: containerWidth, + "& .formControl": { + marginTop: "8px", + marginBottom: "4px", + }, + "& .css-hsm3ra-MuiFormLabel-root": { + color: "rgba(0, 0, 0, 0.6) !important", + }, + "& .MuiRadio-root": { + color: "#1D91AB !important", + marginLeft: "10px", + paddingRight: "7px", + }, + "& #invisibleRadioInput": { + height: 0, + border: "none", + width: 0, + }, + "& .MuiFormHelperText-root": { + color: "#083A50", + marginLeft: 0, + }, + "& .MuiFormHelperText-root.Mui-error": { + color: "#D54309 !important", + }, + "& .displayNone": { + display: "none !important", + }, +})); const StyledFormLabel = styled("label")(() => ({ fontWeight: 700, @@ -148,7 +151,7 @@ const RadioYesNoInput: FC = ({ md={gridWidth || 6} xs={12} item - $containerWidth={containerWidth} + containerWidth={containerWidth} > ` - width: ${(props) => props.$containerWidth}; - .formControl{ - margin-top: 8px; - margin-bottom: 4px; - } - .css-hsm3ra-MuiFormLabel-root { - color: rgba(0, 0, 0, 0.6) !important; - } - .MuiRadio-root{ - color: #1D91AB !important; - margin-left: 10px; - } - - #invisibleRadioInput { - height: 0; - border: none; - width: 0; - } - .MuiFormHelperText-root { - color: #083A50; - margin-left: 0; - } - .MuiFormHelperText-root.Mui-error { - color: #D54309 !important; - } - .displayNone { - display: none !important; - } -`; +const GridStyled = styled(Grid, { + shouldForwardProp: (prop) => prop !== "containerWidth", +})(({ containerWidth }) => ({ + width: containerWidth, + "& .formControl": { + marginTop: "8px", + marginBottom: "4px", + }, + "& .css-hsm3ra-MuiFormLabel-root": { + color: "rgba(0, 0, 0, 0.6) !important", + }, + "& .MuiRadio-root": { + color: "#1D91AB !important", + marginLeft: "10px", + }, + "& #invisibleRadioInput": { + height: 0, + border: "none", + width: 0, + }, + "& .MuiFormHelperText-root": { + color: "#083A50", + marginLeft: 0, + }, + "& .MuiFormHelperText-root.Mui-error": { + color: "#D54309 !important", + }, + "& .displayNone": { + display: "none !important", + }, +})); const StyledFormLabel = styled("label")(() => ({ fontWeight: 700, @@ -111,7 +113,7 @@ const RadioYesNoInput: FC = ({ }, [radioGroupInputRef]); return ( - + {label}