-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Bob Zhao
committed
Oct 11, 2024
1 parent
9a4df85
commit 0158544
Showing
3 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
110 changes: 110 additions & 0 deletions
110
containers/tefca-viewer/src/app/query/components/SelectQuery.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
"use client"; | ||
import React, { useEffect, useState } from "react"; | ||
import { FHIR_SERVERS, USE_CASES, ValueSetItem } from "../../constants"; | ||
import CustomizeQuery from "./CustomizeQuery"; | ||
import SelectSavedQuery from "./selectQuery/SelectSavedQuery"; | ||
|
||
import { QueryResponse } from "@/app/query-service"; | ||
import { Patient } from "fhir/r4"; | ||
import { | ||
fetchQueryResponse, | ||
fetchUseCaseValueSets, | ||
} from "./selectQuery/queryHooks"; | ||
|
||
interface SelectQueryProps { | ||
onSubmit: () => void; // Callback when the user submits the query | ||
goBack: () => void; | ||
selectedQuery: USE_CASES; | ||
setSelectedQuery: React.Dispatch<React.SetStateAction<USE_CASES>>; | ||
patientForQuery: Patient | undefined; | ||
resultsQueryResponse: QueryResponse; | ||
setResultsQueryResponse: React.Dispatch<React.SetStateAction<QueryResponse>>; | ||
fhirServer: FHIR_SERVERS; | ||
setFhirServer: React.Dispatch<React.SetStateAction<FHIR_SERVERS>>; | ||
} | ||
|
||
/** | ||
* @param root0 - SelectQueryProps | ||
* @param root0.onSubmit - Callback for submit action | ||
* @param root0.goBack - Callback to return to previous page | ||
* @param root0.selectedQuery - query we chose for further customization | ||
* @param root0.setSelectedQuery - callback function to update the selected query | ||
* @param root0.patientForQuery - patient to apply a particular query for | ||
* @param root0.resultsQueryResponse - Response of selected query | ||
* @param root0.setResultsQueryResponse - Callback function to update selected | ||
* query | ||
* @param root0.fhirServer - the FHIR server that we're running the query against | ||
* @param root0.setFhirServer - callback function to update the FHIR server | ||
* @returns - The selectQuery component. | ||
*/ | ||
const SelectQuery: React.FC<SelectQueryProps> = ({ | ||
onSubmit, | ||
goBack, | ||
selectedQuery, | ||
setSelectedQuery, | ||
patientForQuery, | ||
resultsQueryResponse, | ||
setResultsQueryResponse, | ||
fhirServer, | ||
setFhirServer, | ||
}) => { | ||
const [showCustomizeQuery, setShowCustomizedQuery] = useState(false); | ||
const [queryValueSets, setQueryValueSets] = useState<ValueSetItem[]>( | ||
[] as ValueSetItem[], | ||
); | ||
useEffect(() => { | ||
// Gate whether we actually update state after fetching so we | ||
// avoid name-change race conditions | ||
let isSubscribed = true; | ||
|
||
fetchUseCaseValueSets(selectedQuery, setQueryValueSets, isSubscribed).catch( | ||
console.error, | ||
); | ||
|
||
// Destructor hook to prevent future state updates | ||
return () => { | ||
isSubscribed = false; | ||
}; | ||
}, [selectedQuery, setQueryValueSets]); | ||
|
||
useEffect(() => { | ||
let isSubscribed = true; | ||
|
||
fetchQueryResponse( | ||
patientForQuery, | ||
selectedQuery, | ||
isSubscribed, | ||
queryValueSets, | ||
setResultsQueryResponse, | ||
fhirServer, | ||
).catch(console.error); | ||
|
||
// Destructor hook to prevent future state updates | ||
return () => { | ||
isSubscribed = false; | ||
}; | ||
}, [patientForQuery, selectedQuery, queryValueSets, setResultsQueryResponse]); | ||
|
||
return showCustomizeQuery ? ( | ||
<CustomizeQuery | ||
useCaseQueryResponse={resultsQueryResponse} | ||
queryType={selectedQuery} | ||
queryValuesets={queryValueSets} | ||
setQueryValuesets={setQueryValueSets} | ||
goBack={() => setShowCustomizedQuery(false)} | ||
></CustomizeQuery> | ||
) : ( | ||
<SelectSavedQuery | ||
goBack={goBack} | ||
selectedQuery={selectedQuery} | ||
setSelectedQuery={setSelectedQuery} | ||
setShowCustomizedQuery={setShowCustomizedQuery} | ||
handleSubmit={onSubmit} | ||
fhirServer={fhirServer} | ||
setFhirServer={setFhirServer} | ||
></SelectSavedQuery> | ||
); | ||
}; | ||
|
||
export default SelectQuery; | ||
export const RETURN_TO_STEP_ONE_LABEL = "Return to Select patient"; |
139 changes: 139 additions & 0 deletions
139
containers/tefca-viewer/src/app/query/components/selectQuery/SelectSavedQuery.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import { | ||
FHIR_SERVERS, | ||
FhirServers, | ||
USE_CASES, | ||
demoQueryOptions, | ||
} from "@/app/constants"; | ||
import { Select, Button } from "@trussworks/react-uswds"; | ||
import { RETURN_TO_STEP_ONE_LABEL } from "../PatientSearchResults"; | ||
import Backlink from "../backLink/Backlink"; | ||
import styles from "./selectQuery.module.css"; | ||
import { useState } from "react"; | ||
|
||
type SelectSavedQueryProps = { | ||
goBack: () => void; | ||
setSelectedQuery: (selectedQuery: USE_CASES) => void; | ||
selectedQuery: string; | ||
setShowCustomizedQuery: (showCustomize: boolean) => void; | ||
handleSubmit: () => void; | ||
fhirServer: FHIR_SERVERS; | ||
setFhirServer: React.Dispatch<React.SetStateAction<FHIR_SERVERS>>; | ||
}; | ||
|
||
/** | ||
* Component to poulate pre-existing queries | ||
* @param root0 - params | ||
* @param root0.goBack - return function for the previous page | ||
* @param root0.selectedQuery - specified query for future customization / | ||
* application | ||
* @param root0.setSelectedQuery - callback function for specified query | ||
* @param root0.setShowCustomizedQuery - toggle to switch to customization | ||
* view | ||
* @param root0.handleSubmit - submit handler | ||
* @param root0.fhirServer - fhir server to apply a query against | ||
* @param root0.setFhirServer - function to update the fhir server | ||
* @returns SelectedSavedQuery component | ||
*/ | ||
const SelectSavedQuery: React.FC<SelectSavedQueryProps> = ({ | ||
goBack, | ||
selectedQuery, | ||
setSelectedQuery, | ||
setShowCustomizedQuery, | ||
handleSubmit, | ||
fhirServer, | ||
setFhirServer, | ||
}) => { | ||
const [showAdvanced, setShowAdvanced] = useState(false); | ||
|
||
return ( | ||
<form className="content-container-smaller-width"> | ||
{/* Back button */} | ||
<div className="text-bold"> | ||
<Backlink onClick={goBack} label={RETURN_TO_STEP_ONE_LABEL} /> | ||
</div> | ||
<h1 className={`${styles.selectQueryHeaderText}`}> | ||
Step 3: Select a query | ||
</h1> | ||
<div | ||
className={`font-sans-md text-light ${styles.selectQueryExplanationText}`} | ||
> | ||
We will request all data related to your selected patient and query. By | ||
only showing relevant data for your query, we decrease the burden on our | ||
systems and protect patient privacy. If you would like to customize the | ||
query response, click on the "customize query" button. | ||
</div> | ||
<h3 className="margin-bottom-3">Query</h3> | ||
<div className={styles.queryRow}> | ||
{/* Select a query drop down */} | ||
<Select | ||
id="querySelect" | ||
name="query" | ||
value={selectedQuery} | ||
onChange={(e) => setSelectedQuery(e.target.value as USE_CASES)} | ||
className={`${styles.queryDropDown}`} | ||
required | ||
> | ||
{demoQueryOptions.map((option) => ( | ||
<option key={option.value} value={option.value}> | ||
{option.label} | ||
</option> | ||
))} | ||
</Select> | ||
<Button | ||
type="button" | ||
className={`usa-button--outline bg-white ${styles.customizeButton}`} | ||
onClick={() => setShowCustomizedQuery(true)} | ||
> | ||
Customize query | ||
</Button> | ||
</div> | ||
|
||
{showAdvanced && ( | ||
<div> | ||
<h3 className="margin-bottom-3">Health Care Organization (HCO)</h3> | ||
<Select | ||
id="fhir_server" | ||
name="fhir_server" | ||
value={fhirServer} | ||
onChange={(e) => setFhirServer(e.target.value as FHIR_SERVERS)} | ||
required | ||
className={`${styles.queryDropDown}`} | ||
> | ||
Select HCO | ||
{FhirServers.map((fhirServer: string) => ( | ||
<option key={fhirServer} value={fhirServer}> | ||
{fhirServer} | ||
</option> | ||
))} | ||
</Select> | ||
</div> | ||
)} | ||
|
||
{!showAdvanced && ( | ||
<div> | ||
<Button | ||
className={`usa-button--unstyled margin-left-auto ${styles.searchCallToActionButton}`} | ||
type="button" | ||
onClick={() => setShowAdvanced(true)} | ||
> | ||
Advanced... | ||
</Button> | ||
</div> | ||
)} | ||
|
||
{/* Submit Button */} | ||
<div className="padding-top-6"> | ||
<Button | ||
type="button" | ||
disabled={!selectedQuery} | ||
className={selectedQuery ? "usa-button" : "usa-button disabled"} | ||
onClick={() => handleSubmit()} | ||
> | ||
Submit | ||
</Button> | ||
</div> | ||
</form> | ||
); | ||
}; | ||
|
||
export default SelectSavedQuery; |
101 changes: 101 additions & 0 deletions
101
containers/tefca-viewer/src/app/query/components/selectQuery/queryHooks.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { | ||
FHIR_SERVERS, | ||
USE_CASES, | ||
UseCaseToQueryName, | ||
ValueSetItem, | ||
} from "@/app/constants"; | ||
import { | ||
getSavedQueryByName, | ||
mapQueryRowsToValueSetItems, | ||
} from "@/app/database-service"; | ||
import { UseCaseQuery, UseCaseQueryResponse } from "@/app/query-service"; | ||
import { Patient } from "fhir/r4"; | ||
|
||
type SetStateCallback<T> = React.Dispatch<React.SetStateAction<T>>; | ||
|
||
/** | ||
* Query to grab valuesets based on use case | ||
* @param selectedQuery - Query use case that's been selected | ||
* @param valueSetStateCallback - state update function to set the valuesets | ||
* @param isSubscribed - state destructor hook to prevent race conditions | ||
*/ | ||
export async function fetchUseCaseValueSets( | ||
selectedQuery: USE_CASES, | ||
valueSetStateCallback: SetStateCallback<ValueSetItem[]>, | ||
isSubscribed: boolean, | ||
) { | ||
if (selectedQuery) { | ||
const queryName = UseCaseToQueryName[selectedQuery as USE_CASES]; | ||
const queryResults = await getSavedQueryByName(queryName); | ||
const vsItems = await mapQueryRowsToValueSetItems(queryResults); | ||
|
||
// Only update if the fetch hasn't altered state yet | ||
if (isSubscribed) { | ||
valueSetStateCallback(vsItems); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Query to apply for future view | ||
* @param patientForQuery - patient to do query against | ||
* @param selectedQuery - query use case | ||
* @param isSubscribed - state destructor hook to prevent race conditions | ||
* @param queryValueSets - valuesets to filter query from default usecase | ||
* @param queryResponseStateCallback - callback function to update state of the | ||
* query response | ||
* @param fhirServer - fhir server to do the querying against | ||
*/ | ||
export async function fetchQueryResponse( | ||
patientForQuery: Patient | undefined, | ||
selectedQuery: USE_CASES, | ||
isSubscribed: boolean, | ||
queryValueSets: ValueSetItem[], | ||
queryResponseStateCallback: SetStateCallback<UseCaseQueryResponse>, | ||
fhirServer: FHIR_SERVERS, | ||
) { | ||
if (patientForQuery && selectedQuery && isSubscribed) { | ||
const patientFirstName = | ||
getNthElementIfDefined(patientForQuery.name, -1)?.given?.join(" ") ?? | ||
"Hyper"; | ||
const patientLastName = | ||
getNthElementIfDefined(patientForQuery.name, -1)?.family ?? "Unlucky"; | ||
|
||
const patientMRN = | ||
getNthElementIfDefined(patientForQuery.identifier)?.value ?? | ||
HYPER_UNLUCKY_MRN; | ||
|
||
const newRequest = { | ||
first_name: patientFirstName as string, | ||
last_name: patientLastName as string, | ||
dob: patientForQuery.birthDate as string, | ||
mrn: patientMRN, | ||
fhir_server: fhirServer, | ||
use_case: selectedQuery, | ||
}; | ||
|
||
const queryResponse = await UseCaseQuery( | ||
newRequest, | ||
queryValueSets.filter((item) => item.include), | ||
{ | ||
Patient: [patientForQuery], | ||
}, | ||
); | ||
|
||
queryResponseStateCallback(queryResponse); | ||
} | ||
} | ||
|
||
function getNthElementIfDefined<T>( | ||
arr: T[] | undefined, | ||
n: number = 0, | ||
): T | undefined { | ||
if (arr && arr.length > n) { | ||
const positionToCheck = n === -1 ? arr.length - 1 : n; | ||
return arr[positionToCheck]; | ||
} else { | ||
return undefined; | ||
} | ||
} | ||
|
||
const HYPER_UNLUCKY_MRN = "8692756"; |