Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CRDCDH-1501 Create/Edit Pre-Approved Study Page #469

Merged
merged 27 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
71e712a
Initialize study view for creating or editing studies
Alejandro-Vega Sep 17, 2024
440b837
Add study icon
Alejandro-Vega Sep 18, 2024
45348ee
Merge branch 'CRDCDH-1500' into CRDCDH-1501
Alejandro-Vega Sep 18, 2024
e7a3bc6
Update access types options to match submission request
Alejandro-Vega Sep 18, 2024
e0e6494
Merge branch '3.1.0' of https://github.com/CBIIT/crdc-datahub-ui into…
Alejandro-Vega Oct 2, 2024
6f2f7c2
Added createApprovedStudy mutation
Alejandro-Vega Oct 2, 2024
57dd1e0
Added API integration for creating an approved study
Alejandro-Vega Oct 2, 2024
0808fb5
Added updateApprovedStudy mutation
Alejandro-Vega Oct 2, 2024
df0eb41
Added getApprovedStudy query
Alejandro-Vega Oct 2, 2024
24e8039
Add support for updating approved study and fix default fields
Alejandro-Vega Oct 2, 2024
32c8a49
Add test coverage for StudyView
Alejandro-Vega Oct 3, 2024
104f415
Adding test coverage for Controller
Alejandro-Vega Oct 3, 2024
96a4964
Merge branch '3.1.0' into CRDCDH-1501
Alejandro-Vega Oct 3, 2024
6377457
Update button text label
Alejandro-Vega Oct 3, 2024
257ec81
Merge branch '3.1.0' into CRDCDH-1501
amattu2 Oct 3, 2024
1b9ccfc
Remove redundant prop
Alejandro-Vega Oct 4, 2024
6000570
Remove required rule for ORCID
Alejandro-Vega Oct 4, 2024
cde0392
Remove redundant prop
Alejandro-Vega Oct 4, 2024
2df9078
Fix test target
Alejandro-Vega Oct 4, 2024
97e2c46
Add suspense loader while retrieving the study
Alejandro-Vega Oct 4, 2024
b9aa91d
Update SuspenseLoader props
Alejandro-Vega Oct 4, 2024
92ecc19
Show suspense loader while auth is loading, and update test
Alejandro-Vega Oct 4, 2024
a56f9ae
Add suspense loader in StudyView while retrieving study, and updated …
Alejandro-Vega Oct 4, 2024
e7af385
Update inputs to be readOnly instead of disabled while saving
Alejandro-Vega Oct 4, 2024
8400ce1
Update resetting form
Alejandro-Vega Oct 4, 2024
9f5f1f1
Update ORCID to be optional and allow saving
Alejandro-Vega Oct 4, 2024
dd7c19a
Update PI field to be optional
Alejandro-Vega Oct 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/assets/icons/study_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
196 changes: 196 additions & 0 deletions src/content/studies/Controller.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import React, { FC, useMemo } from "react";
import { render, waitFor } from "@testing-library/react";
import { MemoryRouter, Routes, Route } from "react-router-dom";
import { MockedProvider, MockedResponse } from "@apollo/client/testing";
import {
Context as AuthContext,
ContextState as AuthContextState,
Status as AuthContextStatus,
} from "../../components/Contexts/AuthContext";
import StudiesController from "./Controller";
import { SearchParamsProvider } from "../../components/Contexts/SearchParamsContext";
import {
GET_APPROVED_STUDY,
GetApprovedStudyInput,
GetApprovedStudyResp,
LIST_APPROVED_STUDIES,
ListApprovedStudiesInput,
ListApprovedStudiesResp,
} from "../../graphql";

// NOTE: Omitting fields depended on by the component
const baseUser: Omit<User, "role"> = {
_id: "",
firstName: "",
lastName: "",
userStatus: "Active",
IDP: "nih",
email: "",
organization: null,
dataCommons: [],
createdAt: "",
updateAt: "",
studies: null,
};

type ParentProps = {
role: User["role"];
initialEntry?: string;
mocks?: MockedResponse[];
ctxStatus?: AuthContextStatus;
children: React.ReactNode;
};

const TestParent: FC<ParentProps> = ({
role,
initialEntry = "/studies",
mocks = [],
ctxStatus = AuthContextStatus.LOADED,
children,
}: ParentProps) => {
const baseAuthCtx: AuthContextState = useMemo<AuthContextState>(
() => ({
status: ctxStatus,
isLoggedIn: role !== null,
user: { ...baseUser, role },
}),
[role, ctxStatus]
);

return (
<MockedProvider mocks={mocks} showWarnings>
<MemoryRouter initialEntries={[initialEntry]}>
<Routes>
<Route
path="/studies/:studyId?"
element={
<AuthContext.Provider value={baseAuthCtx}>
<SearchParamsProvider>{children}</SearchParamsProvider>
</AuthContext.Provider>
}
/>
<Route path="/" element={<div>Root Page</div>} />
</Routes>
</MemoryRouter>
</MockedProvider>
);
};

describe("StudiesController", () => {
it("should render the page without crashing", async () => {
const listApprovedStudiesMock: MockedResponse<
ListApprovedStudiesResp,
ListApprovedStudiesInput
> = {
request: {
query: LIST_APPROVED_STUDIES,
},
variableMatcher: () => true,
result: {
data: {
listApprovedStudies: {
total: 1,
studies: [
{
_id: "study-id-1",
studyName: "Study Name 1",
studyAbbreviation: "SN1",
dbGaPID: "db123456",
controlledAccess: true,
openAccess: false,
PI: "Dr. Smith",
ORCID: "0000-0001-2345-6789",
createdAt: "2022-01-01T00:00:00Z",
originalOrg: "",
},
],
},
},
},
};

const { getByTestId } = render(
<TestParent
role="Admin"
ctxStatus={AuthContextStatus.LOADED}
mocks={[listApprovedStudiesMock]}
>
<StudiesController />
</TestParent>
);

await waitFor(() => {
expect(getByTestId("list-studies-container")).toBeInTheDocument();
});
});

it("should show a loading spinner when the AuthCtx is loading", async () => {
Alejandro-Vega marked this conversation as resolved.
Show resolved Hide resolved
const { getByLabelText } = render(
<TestParent role="Admin" ctxStatus={AuthContextStatus.LOADING}>
<StudiesController />
</TestParent>
);

await waitFor(() => {
expect(getByLabelText("Content Loader")).toBeInTheDocument();
});
});

it.each<UserRole>([
"Data Curator",
"Data Commons POC",
"Federal Lead",
"User",
"fake role" as User["role"],
])("should redirect the user role %p to the home page", (role) => {
const { getByText } = render(
<TestParent role={role}>
<StudiesController />
</TestParent>
);

expect(getByText("Root Page")).toBeInTheDocument();
});

it("should render the StudyView when a studyId param is provided", async () => {
const studyId = "study-id-1";

const getApprovedStudyMock: MockedResponse<GetApprovedStudyResp, GetApprovedStudyInput> = {
request: {
query: GET_APPROVED_STUDY,
},
variableMatcher: () => true,
result: {
data: {
getApprovedStudy: {
_id: studyId,
studyName: "Study Name 1",
studyAbbreviation: "SN1",
dbGaPID: "db123456",
controlledAccess: true,
openAccess: false,
PI: "Dr. Smith",
ORCID: "0000-0001-2345-6789",
createdAt: "2022-01-01T00:00:00Z",
originalOrg: "",
},
},
},
};

const { getByTestId } = render(
<TestParent
role="Admin"
ctxStatus={AuthContextStatus.LOADED}
mocks={[getApprovedStudyMock]}
initialEntry={`/studies/${studyId}`}
>
<StudiesController />
</TestParent>
);

await waitFor(() => {
expect(getByTestId("study-view-container")).toBeInTheDocument();
});
});
});
3 changes: 2 additions & 1 deletion src/content/studies/Controller.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { FC } from "react";
import { Navigate, useParams } from "react-router-dom";
import { useAuthContext } from "../../components/Contexts/AuthContext";
import ListView from "./ListView";
import StudyView from "./StudyView";

/**
* Renders the correct view based on the URL and permissions-tier
Expand All @@ -19,7 +20,7 @@ const StudiesController: FC = () => {
}

if (studyId) {
return null;
return <StudyView _id={studyId} />;
}

return <ListView />;
Expand Down
8 changes: 4 additions & 4 deletions src/content/studies/ListView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ElementType, useRef, useState } from "react";
import { Alert, Button, Container, Stack, styled, TableCell, TableHead } from "@mui/material";
import { Alert, Box, Button, Container, Stack, styled, TableCell, TableHead } from "@mui/material";
import { Link, LinkProps, useLocation } from "react-router-dom";
import { useLazyQuery } from "@apollo/client";
import PageBanner from "../../components/PageBanner";
Expand Down Expand Up @@ -240,7 +240,7 @@ const ListView = () => {
};

return (
<>
<Box data-testid="list-studies-container">
<Container maxWidth="xl">
{(state?.error || error) && (
<Alert sx={{ mt: 2, mx: "auto", p: 2 }} severity="error">
Expand All @@ -256,7 +256,7 @@ const ListView = () => {
body={
<StyledBannerBody direction="row" alignItems="center" justifyContent="flex-end">
<StyledButton component={Link} to="/studies/new">
Add Approved Study
Add Study
</StyledButton>
</StyledBannerBody>
}
Expand All @@ -282,7 +282,7 @@ const ListView = () => {
CustomTableBodyCell={StyledTableCell}
/>
</StyledContainer>
</>
</Box>
);
};

Expand Down
Loading