Skip to content

Commit

Permalink
Merge branch 'mvp-2.1.0' of https://github.com/CBIIT/crdc-datahub-ui
Browse files Browse the repository at this point in the history
…into CRDCDH-852
  • Loading branch information
Alejandro-Vega committed Mar 20, 2024
2 parents a540e82 + 160a7b4 commit e351431
Show file tree
Hide file tree
Showing 15 changed files with 408 additions and 86 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Certify Changes
name: Lint Changes

on:
workflow_dispatch:
Expand All @@ -17,8 +17,8 @@ permissions:
contents: read

jobs:
certify:
name: Certify Build Changes
lint:
name: Lint Changes
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
Expand All @@ -27,7 +27,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "18.x"
node-version: "20.x"
cache: "npm"

- name: Cache node modules
Expand All @@ -44,8 +44,5 @@ jobs:
- name: Run Typecheck
run: npm run check:ci

- name: Run Jest
run: npm run test:ci

- name: Run Linter
run: npm run lint:ci
48 changes: 48 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Test Changes

on:
workflow_dispatch:
pull_request:
branches: "*"
paths:
- "src/**"
- "public/**"
- "*.json"
- "*.js"
- "*.jsx"
- "*.ts"
- "*.tsx"

permissions:
contents: read

jobs:
test:
name: Test Changes
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20.x"
cache: "npm"

- name: Cache node modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-modules-
- name: Install Dependencies
run: npm install --legacy-peer-deps

- name: Run Jest
run: npm run test:ci

- name: Coveralls GitHub Action
uses: coverallsapp/[email protected]
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "TZ=UTC react-scripts test",
"test:ci": "TZ=UTC CI=true react-scripts test --passWithNoTests",
"test:ci": "TZ=UTC CI=true react-scripts test --passWithNoTests --coverage",
"eject": "react-scripts eject",
"lint": "eslint . --ignore-path .gitignore",
"lint:fix": "eslint --fix . --ignore-path .gitignore",
Expand Down
14 changes: 8 additions & 6 deletions src/components/Contexts/AuthContext.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,11 @@ describe("AuthContext > AuthProvider Tests", () => {

const screen = render(<TestParent mocks={mocks} />);

await waitFor(() => expect(screen.getByTestId("first-name").textContent).toEqual("The API updated my first name"));

const cachedUser = JSON.parse(localStorage.getItem("userDetails"));
expect(cachedUser.firstName).toEqual("The API updated my first name");
await waitFor(() => expect(screen.getByTestId("first-name").textContent).toEqual(mocks[0].result.data.getMyUser.firstName));
await waitFor(() => {
const cachedUser = JSON.parse(localStorage.getItem("userDetails"));
expect(cachedUser.firstName).toEqual(mocks[0].result.data.getMyUser.firstName);
}, { timeout: 1000 });
});

it("should logout the user if the BE API call fails", async () => {
Expand Down Expand Up @@ -178,7 +179,8 @@ describe("AuthContext > AuthProvider Tests", () => {

await waitFor(() => expect(screen.getByTestId("isLoggedIn").textContent).toEqual("false"));

expect(screen.getByTestId("isLoggedIn").textContent).toEqual("false");
expect(localStorage.getItem("userDetails")).toBeNull();
await waitFor(() => {
expect(localStorage.getItem("userDetails")).toBeNull();
}, { timeout: 1000 });
});
});
2 changes: 1 addition & 1 deletion src/components/Contexts/FormContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export const FormProvider: FC<ProviderProps> = ({ children, id } : ProviderProps
application: {
_id: newState?.data?.["_id"] === "new" ? undefined : newState?.data?.["_id"],
programName: data?.program?.name,
studyAbbreviation: data?.study?.abbreviation,
studyAbbreviation: data?.study?.abbreviation || data?.study?.name,
questionnaireData: JSON.stringify(data),
}
}
Expand Down
51 changes: 51 additions & 0 deletions src/components/Organizations/StudyTooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { ElementType, FC } from 'react';
import { Typography, styled } from '@mui/material';
import Tooltip from '../Tooltip';
import { formatFullStudyName } from '../../utils';

type Props = {
_id: Organization["_id"];
studies: Organization["studies"];
};

const StyledStudyCount = styled(Typography)<{ component: ElementType }>(({ theme }) => ({
textDecoration: "underline",
cursor: "pointer",
color: theme.palette.primary.main,
}));

const TooltipBody: FC<Props> = ({ _id, studies }) => (
<Typography variant="body1">
{studies?.map(({ studyName, studyAbbreviation }) => (
<React.Fragment key={`${_id}_study_${studyName}`}>
{formatFullStudyName(studyName, studyAbbreviation)}
<br />
</React.Fragment>
))}
</Typography>
);

/**
* Organization list view tooltip for studies
*
* @param Props
* @returns {React.FC}
*/
const StudyTooltip: FC<Props> = ({ _id, studies }) => (
<Tooltip
title={<TooltipBody _id={_id} studies={studies} />}
placement="top"
open={undefined}
onBlur={undefined}
disableHoverListener={false}
arrow
>
<StyledStudyCount variant="body2" component="span">
other
{" "}
{studies.length - 1}
</StyledStudyCount>
</Tooltip>
);

export default StudyTooltip;
12 changes: 4 additions & 8 deletions src/content/dataSubmissions/DataSubmission.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
Button,
Card,
CardActions,
CardActionsProps,
CardContent,
Container,
IconButton,
Expand Down Expand Up @@ -117,13 +116,10 @@ const StyledMainContentArea = styled("div")(() => ({
padding: "21px 40px 0",
}));

const StyledCardActions = styled(CardActions, {
shouldForwardProp: (prop) => prop !== "isVisible"
})<CardActionsProps & { isVisible: boolean; }>(({ isVisible }) => ({
const StyledCardActions = styled(CardActions)(() => ({
"&.MuiCardActions-root": {
visibility: isVisible ? "visible" : "hidden",
paddingTop: 0,
}
},
}));

const StyledTabs = styled(Tabs)(() => ({
Expand Down Expand Up @@ -337,7 +333,7 @@ type Props = {
tab: string;
};

const DataSubmission: FC<Props> = ({ submissionId, tab }) => {
const DataSubmission: FC<Props> = ({ submissionId, tab = URLTabs.DATA_ACTIVITY }) => {
usePageTitle(`Data Submission ${submissionId || ""}`);

const { user } = useAuthContext();
Expand Down Expand Up @@ -593,7 +589,7 @@ const DataSubmission: FC<Props> = ({ submissionId, tab }) => {
<BackButton navigateTo="/data-submissions" text="Back to Data Submissions" />
</StyledMainContentArea>
</StyledCardContent>
<StyledCardActions isVisible={tab === URLTabs.DATA_ACTIVITY}>
<StyledCardActions>
<DataSubmissionActions
submission={data?.getSubmission}
onAction={updateSubmissionAction}
Expand Down
41 changes: 3 additions & 38 deletions src/content/organizations/ListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import {
import { Link, LinkProps, useLocation } from "react-router-dom";
import { Controller, useForm } from 'react-hook-form';
import PageBanner from "../../components/PageBanner";
import Tooltip from '../../components/Tooltip';
import { useOrganizationListContext, Status } from '../../components/Contexts/OrganizationListContext';
import SuspenseLoader from '../../components/SuspenseLoader';
import usePageTitle from '../../hooks/usePageTitle';
import StudyTooltip from '../../components/Organizations/StudyTooltip';

type T = Partial<Organization>;

Expand Down Expand Up @@ -162,12 +162,6 @@ const StyledTablePagination = styled(TablePagination)<{ component: React.Element
background: "#F5F7F8",
});

const StyledStudyCount = styled(Typography)<{ component: ElementType }>(({ theme }) => ({
textDecoration: "underline",
cursor: "pointer",
color: theme.palette.primary.main,
}));

const columns: Column[] = [
{
label: "Name",
Expand All @@ -188,24 +182,9 @@ const columns: Column[] = [

return (
<>
{studies[0].studyAbbreviation}
{studies[0].studyAbbreviation || studies[0].studyName}
{studies.length > 1 && " and "}
{studies.length > 1 && (
<Tooltip
title={<StudyContent _id={_id} studies={studies} />}
placement="top"
open={undefined}
onBlur={undefined}
disableHoverListener={false}
arrow
>
<StyledStudyCount variant="body2" component="span">
other
{" "}
{studies.length - 1}
</StyledStudyCount>
</Tooltip>
)}
{studies.length > 1 && (<StudyTooltip _id={_id} studies={studies} />)}
</>
);
},
Expand All @@ -227,20 +206,6 @@ const columns: Column[] = [
},
];

const StudyContent: FC<{ _id: Organization["_id"], studies: Organization["studies"] }> = ({ _id, studies }) => (
<Typography variant="body1">
{studies?.map(({ studyName, studyAbbreviation }) => (
<React.Fragment key={`${_id}_study_${studyName}`}>
{studyName}
{" ("}
{studyAbbreviation}
{") "}
<br />
</React.Fragment>
))}
</Typography>
);

/**
* View for List of Organizations
*
Expand Down
38 changes: 21 additions & 17 deletions src/content/organizations/OrganizationView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,14 @@ import {
} from '../../graphql';
import ConfirmDialog from '../../components/Organizations/ConfirmDialog';
import usePageTitle from '../../hooks/usePageTitle';
import { formatFullStudyName, mapOrganizationStudyToId } from '../../utils';

type Props = {
_id: Organization["_id"] | "new";
};

type FormInput = Omit<EditOrganizationInput, "studies"> & {
/**
* Select boxes cannot contain objects, using `studyAbbreviation` instead
*/
studies: ApprovedStudy["studyAbbreviation"][];
studies: ApprovedStudy["_id"][];
};

const StyledContainer = styled(Container)({
Expand Down Expand Up @@ -172,6 +170,7 @@ const OrganizationView: FC<Props> = ({ _id }: Props) => {
const activeSubs = dataSubmissions?.filter((ds) => !inactiveSubmissionStatus.includes(ds?.status));

organization?.studies?.forEach((s) => {
// NOTE: The `Submission` type only has `studyAbbreviation`, we cannot compare IDs
if (activeSubs?.some((ds) => ds?.studyAbbreviation === s?.studyAbbreviation)) {
activeStudies[s?.studyAbbreviation] = true;
}
Expand All @@ -193,7 +192,7 @@ const OrganizationView: FC<Props> = ({ _id }: Props) => {
fetchPolicy: "cache-and-network",
});

const { data: approvedStudies } = useQuery<ListApprovedStudiesResp>(LIST_APPROVED_STUDIES, {
const { data: approvedStudies, refetch: refetchStudies } = useQuery<ListApprovedStudiesResp>(LIST_APPROVED_STUDIES, {
context: { clientName: 'backend' },
fetchPolicy: "cache-and-network",
});
Expand Down Expand Up @@ -239,14 +238,14 @@ const OrganizationView: FC<Props> = ({ _id }: Props) => {
const onSubmit = async (data: FormInput) => {
setSaving(true);

const studyAbbrToName: { [studyAbbreviation: string]: Pick<ApprovedStudy, "studyName" | "studyAbbreviation"> } = {};
approvedStudies?.listApprovedStudies?.forEach(({ studyName, studyAbbreviation }) => {
studyAbbrToName[studyAbbreviation] = { studyName, studyAbbreviation };
const studyMap: { [_id: string]: Pick<ApprovedStudy, "studyName" | "studyAbbreviation"> } = {};
approvedStudies?.listApprovedStudies?.forEach(({ _id, studyName, studyAbbreviation }) => {
studyMap[_id] = { studyName, studyAbbreviation };
});

const variables = {
...data,
studies: data.studies.map((abbr) => (studyAbbrToName[abbr])).filter((s) => !!s?.studyName && !!s?.studyAbbreviation),
studies: data.studies.map((_id) => (studyMap[_id]))?.filter((s) => !!s?.studyName) || [],
};

if (_id === "new" && !organization?._id) {
Expand Down Expand Up @@ -313,17 +312,25 @@ const OrganizationView: FC<Props> = ({ _id }: Props) => {

(async () => {
const { data, error } = await getOrganization({ variables: { orgID: _id, organization: _id } });

if (error || !data?.getOrganization) {
navigate("/organizations", { state: { error: "Unable to fetch organization" } });
return;
}

// No studies or original request did not complete. Refetch
let studyList: ApprovedStudy[] = approvedStudies?.listApprovedStudies;
if (!studyList?.length) {
const { data } = await refetchStudies();
studyList = data?.listApprovedStudies;
}

setOrganization(data?.getOrganization);
setDataSubmissions(data?.listSubmissions?.submissions);
setFormValues({
...data?.getOrganization,
studies: data?.getOrganization?.studies?.filter((s) => !!s?.studyName && !!s?.studyAbbreviation).map(({ studyAbbreviation }) => studyAbbreviation) || [],
studies: data?.getOrganization?.studies
?.map((s) => mapOrganizationStudyToId(s, studyList || []))
?.filter((_id) => !!_id) || [],
});
})();
}, [_id]);
Expand Down Expand Up @@ -413,12 +420,9 @@ const OrganizationView: FC<Props> = ({ _id }: Props) => {
inputProps={{ "aria-labelledby": "studiesLabel" }}
multiple
>
{approvedStudies?.listApprovedStudies?.map(({ studyName, studyAbbreviation }) => (
<MenuItem key={studyAbbreviation} value={studyAbbreviation}>
{studyName}
{" ("}
{studyAbbreviation}
{") "}
{approvedStudies?.listApprovedStudies?.map(({ _id, studyName, studyAbbreviation }) => (
<MenuItem key={_id} value={_id}>
{formatFullStudyName(studyName, studyAbbreviation)}
</MenuItem>
))}
</StyledSelect>
Expand Down
Loading

0 comments on commit e351431

Please sign in to comment.