Skip to content

Commit

Permalink
Merge branch 'main' into nickclyde/keycloak-terraform
Browse files Browse the repository at this point in the history
  • Loading branch information
nickclyde authored Feb 3, 2025
2 parents 6df0f46 + 68129de commit 753db84
Show file tree
Hide file tree
Showing 12 changed files with 151 additions and 79 deletions.
2 changes: 1 addition & 1 deletion query-connector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ If the above doesn't work, try replacing `localhost` with `0.0.0.0`.

#### Running the e2e tests locally

Our e2e's are available locally via `npm run test:playright:local`. You'll need to have the app running locally at `localhost:3000` first (ie using `npm run dev` or running `npm run dev:db` and `npm run dev:next` in two separate terminals).
Our e2e's are available locally via `npm run test:playwright:local`. You'll need to have the app running locally at `localhost:3000` first (ie using `npm run dev` or running `npm run dev:db` and `npm run dev:next` in two separate terminals).

For flows that do queries to a FHIR server (ie `/query`), you'll need to use a DB utility to change the address of the local E2E server to whatever localhost port your dev HAPI server is living at. If you have questions, reach out to another eng on the team.

Expand Down
6 changes: 3 additions & 3 deletions query-connector/e2e/alternate_queries.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ test.describe("alternate queries with the Query Connector", () => {
test("query using form-fillable demo patient by phone number", async ({
page,
}) => {
await page.getByRole("button", { name: "Try it out" }).click();
await page.getByRole("link", { name: "Try it out" }).click();
await page.getByRole("button", { name: "Fill fields" }).click();

// Delete Last name and MRN to force phone number as one of the 3 fields
Expand Down Expand Up @@ -52,7 +52,7 @@ test.describe("alternate queries with the Query Connector", () => {

// test("social determinants query with generalized function", async ({
test("cancer query with generalized function", async ({ page }) => {
await page.getByRole("button", { name: "Try it out" }).click();
await page.getByRole("link", { name: "Try it out" }).click();
await page.getByRole("button", { name: "Fill fields" }).click();
// Select FHIR server from drop down
await page.getByRole("button", { name: "Advanced" }).click();
Expand All @@ -79,7 +79,7 @@ test.describe("alternate queries with the Query Connector", () => {
test("form-fillable STI query using generalized function", async ({
page,
}) => {
await page.getByRole("button", { name: "Try it out" }).click();
await page.getByRole("link", { name: "Try it out" }).click();
await page.getByRole("button", { name: "Fill fields" }).click();
// Select FHIR server from drop down
await page.getByRole("button", { name: "Advanced" }).click();
Expand Down
10 changes: 7 additions & 3 deletions query-connector/e2e/customize_query.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ test.describe("querying with the Query Connector", () => {
// Start every test by navigating to the customize query workflow
test.beforeEach(async ({ page }) => {
await page.goto(TEST_URL);
await page.getByRole("button", { name: "Try it out" }).click();
await page.getByRole("link", { name: "Try it out" }).click();

// Check that the info alert is visible and contains the correct text
const alert = page.locator(".custom-alert");
Expand Down Expand Up @@ -86,7 +86,9 @@ test.describe("querying with the Query Connector", () => {
await expect(page.getByText(TEST_PATIENT_NAME)).toBeVisible();
await expect(page.getByText("Patient Identifiers")).toBeVisible();
await expect(
page.getByText(`Medical Record Number: ${TEST_PATIENT.MRN}`),
page.getByText(
`Medical Record Number: St. Worrywart’s Hospital: ${TEST_PATIENT.MRN}`,
),
).toBeVisible();

// Should now just be a single lonely medication request
Expand Down Expand Up @@ -149,7 +151,9 @@ test.describe("querying with the Query Connector", () => {
await expect(page.getByText(TEST_PATIENT_NAME)).toBeVisible();
await expect(page.getByText("Patient Identifiers")).toBeVisible();
await expect(
page.getByText(`Medical Record Number: ${TEST_PATIENT.MRN}`),
page.getByText(
`Medical Record Number: St. Worrywart’s Hospital: ${TEST_PATIENT.MRN}`,
),
).toBeVisible();

// Should be no medication requests available
Expand Down
2 changes: 1 addition & 1 deletion query-connector/e2e/load.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ test("landing page loads", async ({ page }) => {

// Check that interactable elements are present (header and Get Started)
await expect(page.getByRole("link", { name: metadata.title })).toBeVisible();
await expect(page.getByRole("button", { name: "Try it out" })).toBeVisible();
await expect(page.getByRole("link", { name: "Try it out" })).toBeVisible();
});
8 changes: 5 additions & 3 deletions query-connector/e2e/query_workflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ test.describe("querying with the Query Connector", () => {
});

test("unsuccessful user query: no patients", async ({ page }) => {
await page.getByRole("button", { name: "Try it out" }).click();
await page.getByRole("link", { name: "Try it out" }).click();
await page.getByRole("button", { name: "Fill fields" }).click();
await page.getByLabel("First name").fill("Shouldnt");
await page.getByLabel("Last name").fill("Findanyone");
Expand All @@ -41,7 +41,7 @@ test.describe("querying with the Query Connector", () => {
});

test("successful demo user query", async ({ page }) => {
await page.getByRole("button", { name: "Try it out" }).click();
await page.getByRole("link", { name: "Try it out" }).click();

// Check that the info alert is visible and contains the correct text
const alert = page.locator(".custom-alert");
Expand Down Expand Up @@ -101,7 +101,9 @@ test.describe("querying with the Query Connector", () => {
await expect(page.getByText(TEST_PATIENT_NAME)).toBeVisible();
await expect(page.getByText("Patient Identifiers")).toBeVisible();
await expect(
page.getByText(`Medical Record Number: ${TEST_PATIENT.MRN}`),
page.getByText(
`Medical Record Number: St. Worrywart’s Hospital: ${TEST_PATIENT.MRN}`,
),
).toBeVisible();

// Check that the info alert is visible and has updated to the correct text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
formatContact,
formatIdentifier,
formatDate,
formatSex,
} from "../../../../../shared/format-service";
import styles from "./resultsTables.module.scss";
import Table from "@/app/ui/designSystem/table/Table";
Expand Down Expand Up @@ -76,7 +77,7 @@ function formatDemographics(patient: Patient): DataDisplayInfo[] {
},
{
title: "Sex",
value: patient.gender ?? "",
value: formatSex(patient.gender),
},
{
title: "Race",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const ImmunizationTable: React.FC<ImmunizationTableProps> = ({
{immunizations.map((immunization) => (
<tr className={styles.immunizationRow} key={immunization.id}>
<td>{formatDate(immunization.occurrenceDateTime)}</td>
<td>{immunization.vaccineCode.coding?.[0].display}</td>
<td>{immunization.vaccineCode?.coding?.[0].display}</td>
<td>
{immunization.doseQuantity?.value}{" "}
{immunization.doseQuantity?.code}
Expand Down
44 changes: 19 additions & 25 deletions query-connector/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,34 @@
"use client";
import {
ProcessList,
ProcessListItem,
ProcessListHeading,
Button,
} from "@trussworks/react-uswds";
import { useRouter } from "next/navigation";
import Link from "next/link";
import Image from "next/image";
import styles from "./page.module.scss";
/**
* The landing page for the TEFCA Viewer.
* @returns The LandingPage component.
*/
export default function LandingPage() {
const router = useRouter();

const handleGoToDemo = () => {
router.push(`/query`);
};

return (
<div className="main-body display-flex flex-column flex-justify-center">
<div className="gradient-blue-background flex-1">
<div className="container">
<div className="text-holder">
<h1 className={styles.pageSubtitle}>Data collection made easier</h1>
<h2 className={styles.pageContent}>
<p className={styles.pageContent}>
The Query Connector allows your jurisdiction to query a wide
network of healthcare providers through your existing data use
agreements, giving you access to more complete and timely data.
</h2>

<Button
className="next-button margin-bottom-2"
type="button"
</p>
<Link
className="usa-button next-button margin-bottom-2"
id="next-button"
onClick={() => handleGoToDemo()}
href="/query"
>
Try it out
</Button>
</Link>
</div>
<Image
alt="Graphic illustrating what TEFCA is"
Expand All @@ -50,8 +40,10 @@ export default function LandingPage() {
</div>
</div>
<div className="home flex-1">
<h3 className={styles.pageSubtitle}>What is it?</h3>
<h2 className={styles.pageContent}>
<h3 className={styles.pageSubtitle} role="heading" aria-level={2}>
What is it?
</h3>
<p className={styles.pageContent}>
The Query Connector is a data collection tool that uses an intuitive
querying process to help your staff quickly retrieve patient records
and relevant case information from a wide range of healthcare
Expand All @@ -60,27 +52,29 @@ export default function LandingPage() {
the Data Use and Reciprocal Support Agreement (DURSA), while also
supporting innovative standards for data sharing, like the Trusted
Exchange Framework and Common Agreement (TEFCA).
</h2>
<h3 className={styles.pageSubtitle}>How does it work?</h3>
<h2 className={styles.pageContent}>
</p>
<h3 className={styles.pageSubtitle} role="heading" aria-level={2}>
How does it work?
</h3>
<p className={styles.pageContent}>
Public health staff can interact with the Query Connector manually by
entering simple patient details — such as name, date of birth, or
medical identifiers — along with a query, into the web-based portal.
The Query Connector surfaces patient data relevant to the use case in
an easily readable format, making data more usable for case
investigation.
</h2>
</p>
<ProcessList className="padding-top-4">
<ProcessListItem>
<ProcessListHeading type="h4">
<ProcessListHeading type="h3">
Search for a patient
</ProcessListHeading>
<p className="margin-top-05 font-sans-xs">
Based on name, date of birth, and other demographic information
</p>
</ProcessListItem>
<ProcessListItem>
<ProcessListHeading type="h4">
<ProcessListHeading type="h3">
View information tied to your case investigation
</ProcessListHeading>
<p className="font-sans-xs">
Expand Down
66 changes: 41 additions & 25 deletions query-connector/src/app/shared/format-service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,19 @@ export function formatIdentifier(identifier: Identifier[]): JSX.Element {
return (
<>
{identifier.map((id) => {
let idType = id.type?.coding?.[0].display ?? "";
if (idType === "") {
idType = id.type?.text ?? "";
}
let idType = id.type?.coding?.[0]?.display || id.type?.text || "";
let idAssigner = id.assigner?.display || "";
let idValue = id.value || "";

return (
<div key={id.value}>
{" "}
{idType}: {id.value} <br />{" "}
</div>
);
let formattedId =
idType && idAssigner
? `${idType}: ${idAssigner}: ${idValue}`
: idType
? `${idType}: ${idValue}`
: idAssigner
? `${idAssigner}: ${idValue}`
: idValue;
return <div key={idValue}>{formattedId}</div>;
})}
</>
);
Expand All @@ -164,22 +166,17 @@ export function formatMRN(identifier: Identifier[]): JSX.Element {
return (
<>
{identifier.map((id) => {
let mrnFlag = false;
id.type?.coding?.forEach((code) => {
if (code.code === "MR") {
mrnFlag = true;
}
});
if (mrnFlag) {
return (
<div key={id.value}>
{" "}
{id.value} <br />{" "}
</div>
);
}
const isMRN = id.type?.coding?.some((code) => code.code === "MR");
if (!isMRN) return null;

return null;
const idAssigner = id.assigner?.display || "";
const idValue = id.value || "";

return (
<div key={idValue}>
{idAssigner ? `${idAssigner}: ${idValue}` : idValue}
</div>
);
})}
</>
);
Expand Down Expand Up @@ -337,3 +334,22 @@ export function formatCoding(coding: Coding | undefined) {
</>
);
}

/**
* Formats a patient's sex in compliance with Executive order 14168:
* https://www.federalregister.gov/documents/2025/01/30/2025-02090/defending-women-from-gender-ideology-extremism-and-restoring-biological-truth-to-the-federal
* and OPM guidance on the Implementation of Executive Order 14168:
* https://www.chcoc.gov/content/initial-guidance-regarding-president-trump%E2%80%99s-executive-order-defending-women
* @param sex - The patient's sex
* @returns The string "Male", "Female", or "".
*/
export function formatSex(sex: string | undefined): string {
switch (sex) {
case "male":
return "Male";
case "female":
return "Female";
default:
return "";
}
}
23 changes: 22 additions & 1 deletion query-connector/src/app/tests/assets/GoldenSickPatient.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,29 @@
],
"text": "Medical Record Number"
},
"assigner": {
"display": "St. Worrywart’s Hospital"
},
"system": "http://hospital.smarthealthit.org",
"value": "8692756"
},
{
"use": "usual",
"type": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "DL",
"display": "Driver's License"
}
],
"text": "Driver's License"
},
"assigner": {
"display": "Michigan"
},
"system": "http://dmv.gov",
"value": "DL12345"
}
],
"active": true,
Expand Down Expand Up @@ -13622,4 +13643,4 @@
}
}
]
}
}
Loading

0 comments on commit 753db84

Please sign in to comment.