Skip to content

Commit

Permalink
Add a preliminary version of the Cohorts page (#114)
Browse files Browse the repository at this point in the history
* Cohort codegen (#109)

* explicit libdir path

* Cohort, CohortComplete schema updates

Signed-off-by: Angelica Ochoa <15623749+ao508@users.noreply.github.com>

---------

Signed-off-by: Angelica Ochoa <15623749+ao508@users.noreply.github.com>

* Add Cohorts view prototype (#111)

* Perform incremental minor cleanups

- Update the favicon from the template favicon to the Smile logo, which
involves moving the logo images to /public to enable index.html to
access them
- Remove the unused template HomePage component

* Create a new route and page for Cohorts view

* Match functional component declaration syntax across all pages

* Generate new types (e.g. Cohort) and define a query for finding cohort samples

* Make the Samples query inside the Samples component generic

* Display cohort sample data on Cohorts view

* Display the TEMPO data columns in Cohorts view

* Rerun codegen to reflect cohort to sample relationship name change

* Add the remaining Tempo event columns and remove Status

* Modify Cohorts view to show cohort-level data

* Modify RecordsList and SamplesList components to handle cohort samples popup

* Make cohort samples popup read-only & show a lock icon for all read-only fields

* Fix popup shows only samples by cohort and fields are searchable

* Add CMO sample label field to the cohort samples popup

* Clean up GraphQL operations file

* Add '# Samples' column to Cohorts view

* Set cohort samples to display the latest tempo events

* Fix cohort samples popup's export

* Pre-PR cleanups

* Clean up and reorganize `RecordsList` and `SamplesList` components (#112)

* Resize RecordsList table to fill the available height

* Remove unneeded props from RecordsList component

* Rename 'ActiveColumns' to 'ActivePatientsListColumns' to standardize colDefs input names

* Combine RecordsList's props sampleQueryParamFieldName and sampleQueryParamValue into one

* Use title case to display field name in heading of samples popups

* Rename cohort/ to cohorts/ to match patients/ and requests/

* Rename RecordsList's prop 'conditionBuilder' to 'queryFilterWhereVariables' for more clarity

* Rename RecordsList's prop 'getRowData' to 'getSampleRowData' for more clarity

* Standardize search-related state variable names for more clarity

* Rename RecordsList's prop 'customFilterUI' to 'customToolbarUI' for more clarity

* Standardize functional component initialization syntax

* Replace table height inputs with CSS classes

* Standardize function input names of search-related state variable names for more clarity

* Standardize and clean up sample where variables and search handlers

* Reorganize RecordsList and SamplesList props order and naming

* Show `CohortComplete` data of each cohort record on Cohorts page (#113)

* Show Cohort Complete data on Cohorts page

* Show latest CohortComplete data on Cohorts page

* Enable searching for CohortComplete fields

---------

Signed-off-by: Angelica Ochoa <15623749+ao508@users.noreply.github.com>
Co-authored-by: ao508 <15623749+ao508@users.noreply.github.com>
  • Loading branch information
qu8n and ao508 authored Mar 19, 2024
1 parent dfe1063 commit 21a265b
Show file tree
Hide file tree
Showing 23 changed files with 65,614 additions and 31,015 deletions.
File renamed without changes
File renamed without changes
13 changes: 1 addition & 12 deletions frontend/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,8 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- <link-->
<!-- rel="stylesheet"-->
<!-- href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"-->
<!-- integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"-->
<!-- crossorigin="anonymous"-->
<!-- />-->

<link href="/assets/img/favicon.png" rel="icon" />
<link href="/assets/img/apple-touch-icon.png" rel="apple-touch-icon" />
<link href="/img/logo.png" rel="icon" />

<!-- Google Fonts -->
<link href="https://fonts.gstatic.com" rel="preconnect" />
Expand All @@ -34,10 +27,6 @@

<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<title>SMILE Dashboard</title>
</head>
<body class="toggle-sidebar">
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { Routes, Route } from "react-router-dom";
import RequestsPage from "./pages/requests/RequestsPage";
import PatientsPage from "./pages/patients/PatientsPage";
import SamplesPage from "./pages/samples/SamplesPage";
import CohortsPage from "./pages/cohorts/CohortsPage";
import LoginSuccessPage from "./pages/auth/LoginSuccessPage";
import SmileNavBar from "./shared/components/SmileNavBar";
import { getUserEmail } from "./utils/getUserEmail";

function App() {
export default function App() {
const [userEmail, setUserEmail] = useState<string | null>(null);

useEffect(() => {
Expand All @@ -34,11 +35,12 @@ function App() {
<Route path=":cmoPatientId" />
</Route>
<Route path="/samples" element={<SamplesPage />} />
<Route path="/cohorts/" element={<CohortsPage />}>
<Route path=":cohortId" />
</Route>
<Route path="/auth/login-success" element={<LoginSuccessPage />} />
</>
</Routes>
</main>
);
}

export default App;
144 changes: 77 additions & 67 deletions frontend/src/components/RecordsList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import AutoSizer from "react-virtualized-auto-sizer";
import { Button, Container, Modal } from "react-bootstrap";
import { FunctionComponent, useMemo } from "react";
import { Dispatch, SetStateAction, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { DownloadModal } from "./DownloadModal";
import { CSVFormulate } from "../utils/CSVExport";
Expand All @@ -11,58 +11,66 @@ import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import "ag-grid-enterprise";
import { ColDef, IServerSideGetRowsParams } from "ag-grid-community";
import { useHookGeneric } from "../shared/types";
import { SamplesList } from "./SamplesList";
import { SampleWhere } from "../generated/graphql";
import { defaultRecordsColDef } from "../shared/helpers";
import { DataName, useHookLazyGeneric } from "../shared/types";
import SamplesList from "./SamplesList";
import { Sample, SampleWhere } from "../generated/graphql";
import { defaultReadOnlyColDef } from "../shared/helpers";
import { PatientIdsTriplet } from "../pages/patients/PatientsPage";
import { ErrorMessage, LoadingSpinner, Toolbar } from "../shared/tableElements";

export interface IRecordsListProps {
lazyRecordsQuery: typeof useHookGeneric;
nodeName: string;
totalCountNodeName: string;
pageRoute: string;
searchTerm: string;
interface IRecordsListProps {
colDefs: ColDef[];
conditionBuilder: (uniqueQueries: string[]) => Record<string, any>[];
sampleQueryParamValue: string | undefined;
sampleQueryParamFieldName: string;
searchVariables: SampleWhere;
customFilterUI?: JSX.Element;
setCustomFilterVals?: (vals: PatientIdsTriplet[]) => void;
searchVal: string[];
setSearchVal: (val: string[]) => void;
dataName: DataName;
nodeName?: string;
lazyRecordsQuery: typeof useHookLazyGeneric;
lazyRecordsQueryAddlVariables?: Record<string, any>;
queryFilterWhereVariables: (
parsedSearchVals: string[]
) => Record<string, any>[];
userSearchVal: string;
setUserSearchVal: Dispatch<SetStateAction<string>>;
parsedSearchVals: string[];
setParsedSearchVals: Dispatch<SetStateAction<string[]>>;
handleSearch: () => void;
inputVal: string;
setInputVal: (val: string) => void;
showDownloadModal: boolean;
setShowDownloadModal: (val: boolean) => void;
setShowDownloadModal: Dispatch<SetStateAction<boolean>>;
handleDownload: () => void;
samplesQueryParam: string | undefined;
samplesDefaultColDef: ColDef;
getSamplesRowData: (samples: Sample[]) => any[];
samplesColDefs: ColDef[];
samplesParentWhereVariables: SampleWhere;
samplesRefetchWhereVariables: (
samplesParsedSearchVals: string[]
) => SampleWhere;
setCustomSearchVals?: Dispatch<SetStateAction<PatientIdsTriplet[]>>;
customToolbarUI?: JSX.Element;
}

const RecordsList: FunctionComponent<IRecordsListProps> = ({
lazyRecordsQuery,
nodeName,
totalCountNodeName,
pageRoute,
searchTerm,
export default function RecordsList({
colDefs,
conditionBuilder,
sampleQueryParamValue,
sampleQueryParamFieldName,
searchVariables,
customFilterUI,
setCustomFilterVals,
searchVal,
setSearchVal,
dataName,
nodeName = dataName,
lazyRecordsQuery,
lazyRecordsQueryAddlVariables,
queryFilterWhereVariables,
userSearchVal,
setUserSearchVal,
parsedSearchVals,
setParsedSearchVals,
handleSearch,
inputVal,
setInputVal,
showDownloadModal,
setShowDownloadModal,
handleDownload,
}) => {
samplesQueryParam,
samplesDefaultColDef,
getSamplesRowData,
samplesColDefs,
samplesParentWhereVariables,
samplesRefetchWhereVariables,
customToolbarUI,
setCustomSearchVals,
}: IRecordsListProps) {
const [showClosingWarning, setShowClosingWarning] = useState(false);
const [unsavedChanges, setUnsavedChanges] = useState(false);
const navigate = useNavigate();
Expand All @@ -73,19 +81,22 @@ const RecordsList: FunctionComponent<IRecordsListProps> = ({
lazyRecordsQuery({
variables: {
options: { limit: 20, offset: 0 },
...lazyRecordsQueryAddlVariables,
},
});

const totalCountNodeName = `${nodeName}Connection`;

const datasource = useMemo(() => {
return {
// called by the grid when more rows are required
getRows: (params: IServerSideGetRowsParams) => {
const fetchInput = {
where: {
OR: conditionBuilder(searchVal),
OR: queryFilterWhereVariables(parsedSearchVals),
},
[`${nodeName}ConnectionWhere2`]: {
OR: conditionBuilder(searchVal),
OR: queryFilterWhereVariables(parsedSearchVals),
},
options: {
offset: params.request.startRow,
Expand Down Expand Up @@ -114,7 +125,7 @@ const RecordsList: FunctionComponent<IRecordsListProps> = ({
},
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchVal]);
}, [parsedSearchVals]);

if (loading) return <LoadingSpinner />;

Expand All @@ -126,7 +137,7 @@ const RecordsList: FunctionComponent<IRecordsListProps> = ({
if (unsavedChanges) {
setShowClosingWarning(true);
} else {
navigate(pageRoute);
navigate(`/${dataName}`);
}
};

Expand All @@ -138,7 +149,7 @@ const RecordsList: FunctionComponent<IRecordsListProps> = ({
return fetchMore({
variables: {
where: {
OR: conditionBuilder(searchVal),
OR: queryFilterWhereVariables(parsedSearchVals),
},
options: {
offset: 0,
Expand Down Expand Up @@ -183,7 +194,7 @@ const RecordsList: FunctionComponent<IRecordsListProps> = ({
onClick={() => {
setShowClosingWarning(false);
setUnsavedChanges(false);
navigate(pageRoute);
navigate(`/${dataName}`);
}}
>
Continue Exiting
Expand All @@ -192,22 +203,23 @@ const RecordsList: FunctionComponent<IRecordsListProps> = ({
</Modal>
)}

{sampleQueryParamValue && (
{samplesQueryParam && (
<AutoSizer>
{({ height, width }) => (
<Modal show={true} dialogClassName="modal-90w" onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>{`Viewing ${sampleQueryParamValue}`}</Modal.Title>
<Modal.Title>{`Viewing ${samplesQueryParam}`}</Modal.Title>
</Modal.Header>
<Modal.Body>
<div style={{ height: 600 }}>
<div className={styles.popupHeight}>
<SamplesList
height={height * 11}
searchVariables={searchVariables}
columnDefs={samplesColDefs}
defaultColDef={samplesDefaultColDef}
getRowData={getSamplesRowData}
parentWhereVariables={samplesParentWhereVariables}
refetchWhereVariables={samplesRefetchWhereVariables}
setUnsavedChanges={setUnsavedChanges}
exportFileName={`${sampleQueryParamFieldName}_${sampleQueryParamValue}.tsv`}
sampleQueryParamFieldName={sampleQueryParamFieldName}
sampleQueryParamValue={sampleQueryParamValue}
exportFileName={`${samplesQueryParam}.tsv`}
/>
</div>
</Modal.Body>
Expand All @@ -217,26 +229,26 @@ const RecordsList: FunctionComponent<IRecordsListProps> = ({
)}

<Toolbar
searchTerm={searchTerm}
input={inputVal}
setInput={setInputVal}
dataName={dataName}
userSearchVal={userSearchVal}
setUserSearchVal={setUserSearchVal}
handleSearch={handleSearch}
clearInput={() => {
setCustomFilterVals && setCustomFilterVals([]);
setSearchVal([]);
clearUserSearchVal={() => {
setCustomSearchVals && setCustomSearchVals([]);
setParsedSearchVals([]);
}}
matchingResultsCount={`${remoteCount?.toLocaleString()} matching ${
remoteCount > 1 ? searchTerm : searchTerm.slice(0, -1)
remoteCount > 1 ? dataName : dataName.slice(0, -1)
}`}
handleDownload={handleDownload}
customUI={customFilterUI}
customUI={customToolbarUI}
/>

<AutoSizer>
{({ width }) => (
<div
className="ag-theme-alpine"
style={{ height: 540, width: width }}
className={`ag-theme-alpine ${styles.tableHeight}`}
style={{ width: width }}
>
<AgGridReact
rowModelType={"serverSide"}
Expand All @@ -248,7 +260,7 @@ const RecordsList: FunctionComponent<IRecordsListProps> = ({
context={{
navigateFunction: navigate,
}}
defaultColDef={defaultRecordsColDef}
defaultColDef={defaultReadOnlyColDef}
onGridReady={(params) => {
params.api.sizeColumnsToFit();
}}
Expand All @@ -262,6 +274,4 @@ const RecordsList: FunctionComponent<IRecordsListProps> = ({
</AutoSizer>
</Container>
);
};

export default RecordsList;
}
Loading

0 comments on commit 21a265b

Please sign in to comment.