Skip to content

Commit

Permalink
update navigation between query building pages (#217)
Browse files Browse the repository at this point in the history
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
fzhao99 and pre-commit-ci[bot] authored Dec 17, 2024
1 parent fe4696d commit 38f37e2
Show file tree
Hide file tree
Showing 26 changed files with 436 additions and 242 deletions.
1 change: 0 additions & 1 deletion query-connector/e2e/alternate_queries.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ test.describe("alternate queries with the Query Connector", () => {
await expect(
page.getByRole("heading", { name: "Select a query" }),
).toBeVisible();
// await page.getByTestId("Select").selectOption("social-determinants");
await page.getByTestId("Select").selectOption("cancer");
await page.getByRole("button", { name: "Submit" }).click();
await expect(page.getByText("Loading")).toHaveCount(0, { timeout: 10000 });
Expand Down
11 changes: 6 additions & 5 deletions query-connector/src/app/api/query/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import {
USE_CASES,
FHIR_SERVERS,
FhirServers,
UseCases,
UseCaseToQueryName,
USE_CASE_DETAILS,
} from "../../constants";

import { handleRequestError } from "./error-handling-service";
Expand Down Expand Up @@ -75,8 +74,10 @@ export async function POST(request: NextRequest) {
const diagnostics_message = "Missing use_case or fhir_server.";
const OperationOutcome = await handleRequestError(diagnostics_message);
return NextResponse.json(OperationOutcome);
} else if (!Object.values(UseCases).includes(use_case as USE_CASES)) {
const diagnostics_message = `Invalid use_case. Please provide a valid use_case. Valid use_cases include ${UseCases}.`;
} else if (!Object.keys(USE_CASE_DETAILS).includes(use_case)) {
const diagnostics_message = `Invalid use_case. Please provide a valid use_case. Valid use_cases include ${Object.keys(
USE_CASE_DETAILS,
)}.`;
const OperationOutcome = await handleRequestError(diagnostics_message);
return NextResponse.json(OperationOutcome);
} else if (
Expand All @@ -88,7 +89,7 @@ export async function POST(request: NextRequest) {
}

// Lookup default parameters for particular use-case search
const queryName = UseCaseToQueryName[use_case as USE_CASES];
const queryName = USE_CASE_DETAILS[use_case as USE_CASES].queryName;
const queryResults = await getSavedQueryByName(queryName);
const valueSets = unnestValueSetsFromQuery(queryResults);

Expand Down
25 changes: 25 additions & 0 deletions query-connector/src/app/backend/dbClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { PoolConfig, Pool } from "pg";

// Load environment variables from .env and establish a Pool configuration
const dbConfig: PoolConfig = {
connectionString: process.env.DATABASE_URL,
max: 10, // Maximum # of connections in the pool
idleTimeoutMillis: 30000, // A client must sit idle this long before being released
connectionTimeoutMillis: process.env.LOCAL_DB_CLIENT_TIMEOUT
? Number(process.env.LOCAL_DB_CLIENT_TIMEOUT)
: 3000, // Wait this long before timing out when connecting new client
};

let cachedDbClient: Pool | null = null;

/**
* Getter function to retrieve the DB client from a naive cache and create a new one
* if one doesn't exist
* @returns a cached version of the DB client
*/
export const getDbClient = () => {
if (!cachedDbClient) {
cachedDbClient = new Pool(dbConfig);
}
return cachedDbClient;
};
30 changes: 30 additions & 0 deletions query-connector/src/app/backend/query-building.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use server";

import { getDbClient } from "./dbClient";
import { QueryDetailsResult } from "../queryBuilding/utils";
const dbClient = getDbClient();

/**
* Getter function to grab saved query details from the DB
* @param queryId - Query ID to grab data from the db with
* @returns The query name, data, and conditions list from the query table
*/
export async function getSavedQueryDetails(queryId: string) {
const id = queryId;
const queryString = `
select q.query_name, q.id, q.query_data, q.conditions_list
from query q
where q.id = $1;
`;

try {
const result = await dbClient.query(queryString, [id]);
if (result.rows.length > 0) {
return result.rows as unknown as QueryDetailsResult[];
}
console.error("No results found for query:", id);
return [];
} catch (error) {
console.error("Error retrieving query", error);
}
}
112 changes: 24 additions & 88 deletions query-connector/src/app/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,69 +8,31 @@ import {
MedicationAdministration,
MedicationRequest,
} from "fhir/r4";
/**
* The use cases that can be used in the app
*/
export const UseCases = [
"social-determinants",
"newborn-screening",
"syphilis",
"gonorrhea",
"chlamydia",
"cancer",
] as const;
export type USE_CASES = (typeof UseCases)[number];

export const UseCaseToQueryName: {
[key in USE_CASES]: string;
} = {
"social-determinants": "Gather social determinants of health",
"newborn-screening": "Newborn screening follow-up",
syphilis: "Syphilis case investigation",
gonorrhea: "Gonorrhea case investigation",
chlamydia: "Chlamydia case investigation",
cancer: "Cancer case investigation",
};

/**
* Labels and values for the query options dropdown on the query page
*/
export const demoQueryOptions = [
{ value: "cancer", label: "Cancer case investigation" },
{ value: "chlamydia", label: "Chlamydia case investigation" },
{ value: "gonorrhea", label: "Gonorrhea case investigation" },
{ value: "newborn-screening", label: "Newborn screening follow-up" },
// Temporarily remove social determinants
// {
// value: "social-determinants",
// label: "Gather social determinants of health",
// },
{ value: "syphilis", label: "Syphilis case investigation" },
];

type DemoQueryOptionValue = (typeof demoQueryLabels)[number];
export const demoQueryValToLabelMap = demoQueryOptions.reduce(
(acc, curVal) => {
acc[curVal.value as DemoQueryOptionValue] = curVal.label;
return acc;
export const USE_CASE_DETAILS = {
"newborn-screening": {
queryName: "Newborn screening follow-up",
condition: "Newborn Screening",
},
{} as Record<DemoQueryOptionValue, string>,
);
/*
* Map between the queryType property used to define a demo use case's options,
* and the name of that query for purposes of searching the DB.
*/
export const demoQueryLabels = demoQueryOptions.map((dqo) => dqo.label);
export const QueryTypeToQueryName: {
[key in (typeof demoQueryLabels)[number]]: string;
} = {
"Gather social determinants of health": "Social Determinants of Health",
"Newborn screening follow-up": "Newborn Screening",
"Syphilis case investigation": "Congenital syphilis (disorder)",
"Gonorrhea case investigation": "Gonorrhea (disorder)",
"Chlamydia case investigation": "Chlamydia trachomatis infection (disorder)",
"Cancer case investigation": "Cancer (Leukemia)",
};
syphilis: {
queryName: "Syphilis case investigation",
condition: "Congenital syphilis (disorder)",
},
gonorrhea: {
queryName: "Gonorrhea case investigation",
condition: "Gonorrhea (disorder)",
},
chlamydia: {
queryName: "Chlamydia case investigation",
condition: "Chlamydia trachomatis infection (disorder)",
},
cancer: {
queryName: "Cancer case investigation",
condition: "Cancer (Leukemia)",
},
} as const;

export type USE_CASES = keyof typeof USE_CASE_DETAILS;

/**
* The FHIR servers that can be used in the app
Expand Down Expand Up @@ -107,7 +69,6 @@ export type PatientType =
| "newborn-screening-technical-fail"
| "newborn-screening-referral"
| "newborn-screening-pass"
| "social-determinants"
| "sti-syphilis-positive";

export const DEFAULT_DEMO_FHIR_SERVER = "HELIOS Meld: Direct";
Expand All @@ -131,26 +92,7 @@ export const demoData: Record<PatientType, DemoDataFields> = {
cancer: { ...hyperUnluckyPatient, UseCase: "cancer" },
"sti-chlamydia-positive": { ...hyperUnluckyPatient, UseCase: "chlamydia" },
"sti-gonorrhea-positive": { ...hyperUnluckyPatient, UseCase: "gonorrhea" },
"social-determinants": {
...hyperUnluckyPatient,
UseCase: "social-determinants",
},
"sti-syphilis-positive": { ...hyperUnluckyPatient, UseCase: "syphilis" },

// Newborn screening data remains unchanged
// We need to figure how to display specific cases for specific referral, fail, pass
// "newborn-screening-technical-fail": {
// ...hyperUnluckyPatient,
// UseCase: "newborn-screening",
// },
// "newborn-screening-referral": {
// ...hyperUnluckyPatient,
// UseCase: "newborn-screening",
// },
// "newborn-screening-pass": {
// ...hyperUnluckyPatient,
// UseCase: "newborn-screening",
// },
"newborn-screening-technical-fail": {
FirstName: "Mango",
LastName: "Smith",
Expand Down Expand Up @@ -215,12 +157,6 @@ export const patientOptions: Record<string, Option[]> = {
label: "A newborn with a passed screening",
},
],
"social-determinants": [
{
value: "social-determinants",
label: "A patient with housing insecurity",
},
],
syphilis: [
{
value: "sti-syphilis-positive",
Expand Down Expand Up @@ -300,7 +236,7 @@ export const stateOptions = [
export type Mode = "search" | "results" | "select-query" | "patient-results";

/* Mode that query building pages can be in; determines what is displayed to the user */
export type BuildStep = "condition" | "valueset" | "concept";
export type BuildStep = "selection" | "condition" | "valueset";

export const metadata = {
title: "Query Connector",
Expand Down
11 changes: 2 additions & 9 deletions query-connector/src/app/database-service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"use server";
import { Pool, PoolConfig } from "pg";
import {
Bundle,
OperationOutcome,
Expand Down Expand Up @@ -45,6 +44,7 @@ import {
ValuesetStruct,
ValuesetToConceptStruct,
} from "./seedSqlStructs";
import { getDbClient } from "./backend/dbClient";

const getQuerybyNameSQL = `
select q.query_name, q.id, q.query_data, q.conditions_list
Expand All @@ -61,14 +61,7 @@ SELECT c.display, c.code_system, c.code, vs.name as valueset_name, vs.id as valu
WHERE ctvs.condition_id IN (
`;

// Load environment variables from .env and establish a Pool configuration
const dbConfig: PoolConfig = {
connectionString: process.env.DATABASE_URL,
max: 10, // Maximum # of connections in the pool
idleTimeoutMillis: 30000, // A client must sit idle this long before being released
connectionTimeoutMillis: 3000, // Wait this long before timing out when connecting new client
};
const dbClient = new Pool(dbConfig);
const dbClient = getDbClient();

/**
* Executes a search for a ValueSets and Concepts against the Postgres
Expand Down
6 changes: 2 additions & 4 deletions query-connector/src/app/query-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,8 @@ async function generalizedQuery(
const builtQuery = new CustomQuery(querySpec, patientId);
let response: fetch.Response | fetch.Response[];

// Special cases for plain SDH or newborn screening, which just use one query
if (useCase === "social-determinants") {
response = await fhirClient.get(builtQuery.getQuery("social"));
} else if (useCase === "newborn-screening") {
// Special cases for newborn screening, which just use one query
if (useCase === "newborn-screening") {
response = await fhirClient.get(builtQuery.getQuery("observation"));
} else {
const queryRequests: string[] = builtQuery.getAllQueries();
Expand Down
4 changes: 2 additions & 2 deletions query-connector/src/app/query/components/CustomizeQuery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
DibbsConceptType,
DibbsValueSetType,
USE_CASES,
USE_CASE_DETAILS,
ValueSet,
demoQueryValToLabelMap,
} from "../../constants";
import { UseCaseQueryResponse } from "@/app/query-service";
import LoadingView from "./LoadingView";
Expand Down Expand Up @@ -199,7 +199,7 @@ const CustomizeQuery: React.FC<CustomizeQueryProps> = ({
<LoadingView loading={!useCaseQueryResponse} />
<h1 className="page-title margin-bottom-05-important">Customize query</h1>
<h2 className="page-explainer margin-y-0-important">
Query: {demoQueryValToLabelMap[queryType]}
Query: {USE_CASE_DETAILS[queryType].condition}
</h2>
<h3 className="margin-y-0-important font-sans-sm text-light padding-bottom-0 padding-top-05">
{countLabs} labs found, {countMedications} medications found,{" "}
Expand Down
4 changes: 2 additions & 2 deletions query-connector/src/app/query/components/ResultsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import EncounterTable from "./resultsView/tableComponents/EncounterTable";
import MedicationRequestTable from "./resultsView/tableComponents/MedicationRequestTable";
import ObservationTable from "./resultsView/tableComponents/ObservationTable";
import Backlink from "./backLink/Backlink";
import { USE_CASES, demoQueryValToLabelMap } from "@/app/constants";
import { USE_CASES, USE_CASE_DETAILS } from "@/app/constants";
import {
PAGE_TITLES,
RETURN_LABEL,
Expand Down Expand Up @@ -78,7 +78,7 @@ const ResultsView: React.FC<ResultsViewProps> = ({
<h2 className="page-explainer margin-bottom-3-important margin-top-0-important">
<strong>Query: </strong>
<span className="text-normal display-inline-block">
{demoQueryValToLabelMap[selectedQuery]}
{USE_CASE_DETAILS[selectedQuery].condition}
</span>
</h2>

Expand Down
5 changes: 3 additions & 2 deletions query-connector/src/app/query/components/SelectQuery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { useEffect, useState } from "react";
import {
FHIR_SERVERS,
USE_CASES,
UseCaseToQueryName,
USE_CASE_DETAILS,
ValueSet,
} from "../../constants";
import CustomizeQuery from "./CustomizeQuery";
Expand Down Expand Up @@ -78,7 +78,8 @@ const SelectQuery: React.FC<SelectQueryProps> = ({

const fetchDataAndUpdateState = async () => {
if (selectedQuery) {
const queryName = UseCaseToQueryName[selectedQuery as USE_CASES];
const queryName =
USE_CASE_DETAILS[selectedQuery as USE_CASES].queryName;
const valueSets = await fetchUseCaseValueSets(queryName);
// Only update if the fetch hasn't altered state yet
if (isSubscribed) {
Expand Down
6 changes: 1 addition & 5 deletions query-connector/src/app/query/components/header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,4 @@ export default function HeaderComponent() {
);
}

const LOGGED_IN_PATHS = [
"/query",
"/queryBuilding",
"/queryBuilding/buildFromTemplates",
];
const LOGGED_IN_PATHS = ["/query", "/queryBuilding"];
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
FHIR_SERVERS,
FhirServers,
USE_CASES,
demoQueryOptions,
USE_CASE_DETAILS,
} from "@/app/constants";
import { Select, Button } from "@trussworks/react-uswds";
import Backlink from "../backLink/Backlink";
Expand Down Expand Up @@ -75,9 +75,9 @@ const SelectSavedQuery: React.FC<SelectSavedQueryProps> = ({
<option value="" disabled>
Select query
</option>
{demoQueryOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
{Object.entries(USE_CASE_DETAILS).map(([useCase, useCaseDetails]) => (
<option key={useCase} value={useCase}>
{useCaseDetails.queryName}
</option>
))}
</Select>
Expand Down
Loading

0 comments on commit 38f37e2

Please sign in to comment.