Skip to content

Commit

Permalink
Merge pull request #378 from CBIIT/2.1.0/CRDCDH-1180
Browse files Browse the repository at this point in the history
  • Loading branch information
amattu2 authored May 23, 2024
2 parents 91a855e + 4944633 commit 08740fb
Show file tree
Hide file tree
Showing 8 changed files with 378 additions and 38 deletions.
249 changes: 249 additions & 0 deletions src/assets/dataSubmissions/blurred_data_visualization.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 4 additions & 7 deletions src/components/DataSubmissions/LegendItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,16 @@ type Props = {
const StyledStack = styled(Stack, { shouldForwardProp: (p) => p !== "disabled" })<{ disabled: boolean }>(({ disabled }) => ({
opacity: disabled ? 0.4 : 1,
textDecoration: disabled ? "line-through" : "none",
marginRight: "35px",
"&:last-child": {
marginRight: "0",
},
cursor: "pointer",
userSelect: "none",
}));

const StyledLabel = styled(Typography)({
color: "#383838",
fontSize: "11px",
fontFamily: "'Nunito Sans', 'Rubik', sans-serif",
fontWeight: 600,
fontFamily: "'Nunito Sans'",
fontSize: "11px",
lineHeight: "27px",
color: "#383838",
});

const StyledColorBox = styled(Box, { shouldForwardProp: (p) => p !== "color" })<{ color: string }>(({ color }) => ({
Expand Down
49 changes: 32 additions & 17 deletions src/components/DataSubmissions/StatisticLegend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,28 @@ const StyledContainer = styled(Stack)({
borderRadius: "8px",
background: "#F5F8F9",
border: "1px solid #939393",
width: "600px",
padding: "8px 0",
width: "569px",
padding: "13px 32px 12.79px",
marginTop: "20px",
gap: "2.21px",
});

const StyledLegendTitle = styled(Typography)({
fontFamily: "Nunito",
const StyledLegendHeader = styled(Typography)({
fontFamily: "'Nunito Sans', 'Rubik', sans-serif",
fontWeight: 600,
fontSize: "13px",
lineHeight: "17.73px",
letterSpacing: "-0.25px",
color: "#525252",
});

const StyledLegendTitle = styled(Typography)({
fontFamily: "'Nunito Sans', 'Rubik', sans-serif",
fontWeight: 600,
fontSize: "13px",
lineHeight: "17.73px",
letterSpacing: "-0.25px",
color: "#156071",
marginLeft: "-48px",
marginRight: "30px",
});

/**
Expand All @@ -31,17 +41,22 @@ const StyledLegendTitle = styled(Typography)({
* @returns {React.FC}
*/
const StatisticLegend: FC<Props> = ({ filters, onClick }) => (
<StyledContainer direction="row" justifyContent="center" alignItems="center">
<StyledLegendTitle>Validation Status</StyledLegendTitle>
{filters.map((filter) => (
<LegendItem
key={filter.label}
color={filter.color}
label={filter.label}
disabled={filter.disabled}
onClick={() => onClick?.(filter)}
/>
))}
<StyledContainer direction="column" justifyContent="center" alignItems="center">
<StyledLegendHeader variant="h6">
Click a Validation Status to Filter the Node Displays above
</StyledLegendHeader>
<Stack direction="row" justifyContent="center" alignItems="center" gap="35px">
<StyledLegendTitle>Validation Status</StyledLegendTitle>
{filters.map((filter) => (
<LegendItem
key={filter.label}
color={filter.color}
label={filter.label}
disabled={filter.disabled}
onClick={() => onClick?.(filter)}
/>
))}
</Stack>
</StyledContainer>
);

Expand Down
48 changes: 36 additions & 12 deletions src/components/DataSubmissions/ValidationStatistics.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import React, { FC, useMemo, useState } from 'react';
import { cloneDeep, isEqual } from 'lodash';
import { Box, Stack, Tab, Tabs, Typography, styled } from '@mui/material';
import { Box, Stack, StackProps, Tab, Tabs, Typography, styled } from '@mui/material';
import ContentCarousel from '../Carousel';
import NodeTotalChart from '../NodeTotalChart';
import MiniPieChart from '../NodeChart';
import SuspenseLoader from '../SuspenseLoader';
import { buildMiniChartSeries, buildPrimaryChartSeries, compareNodeStats } from '../../utils';
import StatisticLegend from './StatisticLegend';
import blurredDataVisualizationSvg from "../../assets/dataSubmissions/blurred_data_visualization.svg";

type Props = {
dataSubmission: Submission;
statistics: SubmissionStatistic[];
};

const StyledChartArea = styled(Stack)({
const StyledChartArea = styled(Stack, {
shouldForwardProp: (prop) => prop !== "hasNoData",
})<StackProps & { hasNoData?: boolean }>(({ hasNoData }) => ({
height: "422px",
display: "flex",
justifyContent: "center",
Expand All @@ -25,19 +28,32 @@ const StyledChartArea = styled(Stack)({
"& > *": {
zIndex: 10
},
"&::after": {
"&::before": {
position: "absolute",
content: '""',
width: "1440px",
height: "100%",
background: "#FFFFFF",
left: "-100%",
right: "-100%",
top: "0",
bottom: "0",
left: "50%",
transform: "translateX(-50%)",
boxShadow: "0px 4px 20px 0px #00000059",
zIndex: 1,
},
});
...(hasNoData && {
"&::after": {
position: "absolute",
content: `url(${blurredDataVisualizationSvg})`,
width: "100%",
height: "344px",
background: "transparent",
top: "0",
left: "50%",
transform: "translateX(-50%)",
zIndex: 2,
},
}),
}));

const StyledSectionTitle = styled(Typography)({
marginTop: "5px",
Expand All @@ -49,10 +65,14 @@ const StyledSectionTitle = styled(Typography)({
});

const StyledNoData = styled(Typography)({
fontSize: "16px",
width: "944px",
fontSize: "21px",
fontWeight: 400,
fontFamily: "Nunito",
color: "#083A50",
lineHeight: "29px",
fontFamily: "'Nunito', 'Rubik', sans-serif",
color: "#595959",
filter: "drop-shadow(0px 0px 85px #FFF)",
textAlign: "center",
userSelect: "none",
});

Expand Down Expand Up @@ -124,8 +144,12 @@ const DataSubmissionStatistics: FC<Props> = ({ dataSubmission, statistics }: Pro

if (!dataset?.some((s) => s.total > 0)) {
return (
<StyledChartArea direction="row">
<StyledNoData variant="h6">No data has been successfully uploaded yet.</StyledNoData>
<StyledChartArea direction="row" hasNoData>
<StyledNoData variant="h6">
This is the data submission visualization section which displays validation results for
uploaded data. After uploading and validating the data (see below), the visualization
graphic will display.
</StyledNoData>
</StyledChartArea>
);
}
Expand Down
5 changes: 5 additions & 0 deletions src/content/dataSubmissions/ErrorDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ type Props = {
closeText?: string;
errors: string[];
errorCount?: string;
nodeInfo?: string;
uploadedDate?: string;
onClose?: () => void;
} & Omit<DialogProps, "onClose">;
Expand All @@ -117,6 +118,7 @@ const ErrorDialog = ({
closeText = "Close",
errors,
errorCount,
nodeInfo,
uploadedDate,
onClose,
open,
Expand Down Expand Up @@ -148,6 +150,9 @@ const ErrorDialog = ({
{FormatDate(uploadedDate, "M/D/YYYY", "N/A")}
</StyledUploadedDate>
)}
{nodeInfo && (
<StyledUploadedDate>{nodeInfo}</StyledUploadedDate>
)}
<StyledErrorDetails direction="column" spacing={2.5}>
<StyledSubtitle variant="body2">
{errorCount || `${errors?.length || 0} ${errors?.length === 1 ? "ERROR" : "ERRORS"}`}
Expand Down
5 changes: 3 additions & 2 deletions src/content/dataSubmissions/QualityControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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, capitalizeFirstLetter } from "../../utils";
import { FormatDate, titleCase } from "../../utils";
import ErrorDialog from "./ErrorDialog";
import QCResultsContext from "./Contexts/QCResultsContext";

Expand Down Expand Up @@ -347,7 +347,8 @@ const QualityControl: FC<Props> = ({ submission }: Props) => {
open={openErrorDialog}
onClose={() => setOpenErrorDialog(false)}
header={null}
title={`Validation Issues for ${capitalizeFirstLetter(selectedRow?.type)} Node ID ${selectedRow?.submittedID}.`}
title="Validation Issues"
nodeInfo={`For ${titleCase(selectedRow?.type)}${selectedRow?.type?.toLocaleLowerCase() !== "data file" ? " Node" : ""} ID ${selectedRow?.submittedID}`}
errors={allDescriptions}
errorCount={`${allDescriptions?.length || 0} ${allDescriptions?.length === 1 ? "ISSUE" : "ISSUES"}`}
/>
Expand Down
34 changes: 34 additions & 0 deletions src/utils/stringUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,37 @@ describe("filterPositiveIntegerString utility function", () => {
expect(result).toEqual("1");
});
});

describe('titleCase', () => {
it('should capitalize the first letter of each word', () => {
expect(utils.titleCase('data file')).toBe('Data File');
});

it('should handle single word strings', () => {
expect(utils.titleCase('participant')).toBe('Participant');
});

it('should handle empty strings', () => {
expect(utils.titleCase('')).toBe('');
});

it('should safely handle null values', () => {
expect(utils.titleCase(null)).toBe('');
});

it('should safely handle undefined values', () => {
expect(utils.titleCase(undefined)).toBe('');
});

it('should safely handle non-string values', () => {
expect(utils.titleCase(["this isnt a string"] as unknown as string)).toBe('');
});

it('should handle strings with multiple spaces', () => {
expect(utils.titleCase('data file')).toBe('Data File');
});

it('should handle strings with mixed case', () => {
expect(utils.titleCase('dATa fiLE')).toBe('Data File');
});
});
15 changes: 15 additions & 0 deletions src/utils/stringUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,21 @@
*/
export const capitalizeFirstLetter = (str: string): string => (str ? str[0].toUpperCase() + str.slice(1) : "");

/**
* Capitalizes the first letter of each word in a given string.
*
* @see Utilizes {@link capitalizeFirstLetter} to capitalize each word.
* @param str - The string to capitalize.
* @returns The capitalized string.
*/
export const titleCase = (str: string): string => {
if (typeof str !== "string") {
return "";
}

return str.toLowerCase().split(" ").map((word) => capitalizeFirstLetter(word)).join(" ");
};

/**
* 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.
Expand Down

0 comments on commit 08740fb

Please # to comment.