From 640fc357d18346f259b56b114a674eaef6e8d48d Mon Sep 17 00:00:00 2001 From: Bob Zhao Date: Wed, 16 Oct 2024 10:50:28 -0400 Subject: [PATCH 1/5] redirect query --- query-connector/e2e/example.spec.ts | 20 -- query-connector/e2e/query_workflow.spec.ts | 269 +++-------------- query-connector/src/app/CustomQuery.ts | 2 +- query-connector/src/app/constants.ts | 25 +- query-connector/src/app/layout.tsx | 5 - .../app/query/components/CustomizeQuery.tsx | 17 +- .../query/components/PatientSearchResults.tsx | 77 ++--- .../src/app/query/components/ResultsView.tsx | 21 +- .../app/query/components/header/header.tsx | 5 +- .../PatientSearchResultsTable.tsx | 9 +- .../resultsView/ResultsViewTable.tsx | 2 +- .../components/searchForm/SearchForm.tsx | 285 +++++++----------- .../searchForm/searchForm.module.css | 15 +- .../components/selectQuery/selectQuery.tsx | 167 ---------- .../src/app/query/designSystem/SiteAlert.tsx | 12 +- query-connector/src/app/query/page.tsx | 141 +++------ query-connector/src/app/query/test/page.tsx | 80 ----- query-connector/src/styles/custom-styles.scss | 6 - query-connector/src/styles/layout.scss | 2 +- 19 files changed, 298 insertions(+), 862 deletions(-) delete mode 100644 query-connector/e2e/example.spec.ts delete mode 100644 query-connector/src/app/query/components/selectQuery/selectQuery.tsx delete mode 100644 query-connector/src/app/query/test/page.tsx diff --git a/query-connector/e2e/example.spec.ts b/query-connector/e2e/example.spec.ts deleted file mode 100644 index b60fe7cd0..000000000 --- a/query-connector/e2e/example.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { test, expect } from "@playwright/test"; - -test("has title", async ({ page }) => { - await page.goto("https://playwright.dev/"); - - // Expect a title "to contain" a substring. - await expect(page).toHaveTitle(/Playwright/); -}); - -test("get started link", async ({ page }) => { - await page.goto("https://playwright.dev/"); - - // Click the get started link. - await page.getByRole("link", { name: "Get started" }).click(); - - // Expects page to have a heading with the name of Installation. - await expect( - page.getByRole("heading", { name: "Installation" }), - ).toBeVisible(); -}); diff --git a/query-connector/e2e/query_workflow.spec.ts b/query-connector/e2e/query_workflow.spec.ts index 5fd47b08f..fd7d80b1f 100644 --- a/query-connector/e2e/query_workflow.spec.ts +++ b/query-connector/e2e/query_workflow.spec.ts @@ -2,48 +2,24 @@ import { test, expect } from "@playwright/test"; import { TEST_URL } from "../playwright-setup"; - -test.describe("querying with the TryTEFCA viewer", () => { +import { STEP_ONE_PAGE_TITLE } from "@/app/query/components/searchForm/SearchForm"; +import { + CONTACT_US_DISCLAIMER_EMAIL, + CONTACT_US_DISCLAIMER_TEXT, +} from "@/app/query/designSystem/SiteAlert"; +import { TEST_PATIENT, TEST_PATIENT_NAME } from "./constants"; + +test.describe("querying with the Query Connector", () => { test.beforeEach(async ({ page }) => { // Start every test on our main landing page await page.goto(TEST_URL); }); - test("landing page loads", async ({ page }) => { - // Check that each expected text section is present - await expect( - page.getByRole("heading", { name: "Data collection made easier" }), - ).toBeVisible(); - await expect( - page.getByRole("heading", { name: "What is it?" }), - ).toBeVisible(); - await expect( - page.getByRole("heading", { name: "How does it work?" }), - ).toBeVisible(); - - // Check that interactable elements are present (header and Get Started) - await expect( - page.getByRole("link", { name: "TEFCA Viewer" }), - ).toBeVisible(); - await expect( - page.getByRole("button", { name: "Go to the demo" }), - ).toBeVisible(); - }); - test("unsuccessful user query: no patients", async ({ page }) => { await page.getByRole("button", { name: "Go to the demo" }).click(); - await page - .getByLabel("Query", { exact: true }) - .selectOption("social-determinants"); - await page.getByRole("button", { name: "Advanced" }).click(); - await page - .getByLabel("FHIR Server (QHIN)", { exact: true }) - .selectOption("HELIOS Meld: Direct"); - - await page.getByLabel("First Name").fill("Ellie"); - await page.getByLabel("Last Name").fill("Williams"); - await page.getByLabel("Phone Number").fill("5555555555"); - await page.getByLabel("Medical Record Number").fill("TLOU1TLOU2"); + await page.getByRole("button", { name: "Fill fields" }).click(); + await page.getByLabel("First Name").fill("Shouldnt"); + await page.getByLabel("Last Name").fill("Findanyone"); await page.getByRole("button", { name: "Search for patient" }).click(); // Better luck next time, user! @@ -58,9 +34,7 @@ test.describe("querying with the TryTEFCA viewer", () => { .click(); }); - test("successful demo user query: the quest for watermelon mcgee", async ({ - page, - }) => { + test("successful demo user query", async ({ page }) => { await page.getByRole("button", { name: "Go to the demo" }).click(); // Check that the info alert is visible and contains the correct text @@ -70,225 +44,78 @@ test.describe("querying with the TryTEFCA viewer", () => { "This site is for demo purposes only. Please do not enter PII on this website.", ); await expect( - page.getByRole("heading", { name: "Search for a Patient", exact: true }), + page.getByRole("heading", { name: STEP_ONE_PAGE_TITLE, exact: true }), ).toBeVisible(); - // Put in the search parameters for the elusive fruit person - await page - .getByLabel("Query", { exact: true }) - .selectOption("newborn-screening"); - await page - .getByLabel("Patient", { exact: true }) - .selectOption("newborn-screening-referral"); await page.getByRole("button", { name: "Fill fields" }).click(); - await page.getByLabel("First Name").fill("Watermelon"); - await page.getByLabel("Last Name").fill("McGee"); - await page.getByLabel("Date of Birth").fill("2024-07-12"); - await page.getByLabel("Medical Record Number").fill("18091"); - await page.getByLabel("Phone Number").fill("5555555555"); + await page.getByLabel("First Name").fill(TEST_PATIENT.FirstName); + await page.getByLabel("Last Name").fill(TEST_PATIENT.LastName); + await page.getByLabel("Date of Birth").fill(TEST_PATIENT.DOB); + await page.getByLabel("Medical Record Number").fill(TEST_PATIENT.MRN); + await page.getByLabel("Phone Number").fill(TEST_PATIENT.Phone); await page.getByRole("button", { name: "Search for patient" }).click(); await expect(page.getByText("Loading")).toHaveCount(0, { timeout: 10000 }); - // Make sure we have a results page with a single patient - // Non-interactive 'div' elements in the table should be located by text - await expect( - page.getByRole("heading", { name: "Patient Record" }), - ).toBeVisible(); - await expect(page.getByText("Patient Name")).toBeVisible(); - await expect(page.getByText("WATERMELON SPROUT MCGEE")).toBeVisible(); - await expect(page.getByText("Patient Identifiers")).toBeVisible(); - await expect(page.getByText("MRN: 18091")).toBeVisible(); - - // Check that the info alert is visible and has updated to the correct text - const alert2 = page.locator(".custom-alert"); - await expect(alert2).toBeVisible(); - await expect(alert2).toHaveText( - "Interested in learning more about using the TEFCA Query Connector for your jurisdiction? Send us an email at dibbs@cdc.gov", - ); - - // Check to see if the accordion button is open + await page.getByRole("link", { name: "Select patient" }).click(); await expect( - page.getByRole("button", { name: "Observations", expanded: true }), + page.getByRole("heading", { name: "Select a query" }), ).toBeVisible(); + await page.getByTestId("Select").selectOption("chlamydia"); - // We can also just directly ask the page to find us filtered table rows - await expect(page.locator("tbody").locator("tr")).toHaveCount(5); - - // Now let's use the return to search to go back to a blank form - await page.getByRole("button", { name: "New patient search" }).click(); + await page.getByRole("button", { name: "Customize Query" }).click(); await expect( - page.getByRole("heading", { name: "Search for a Patient", exact: true }), + page.getByRole("heading", { name: "Customize Query" }), ).toBeVisible(); - }); - test("query using form-fillable demo patient by phone number", async ({ - page, - }) => { - await page.getByRole("button", { name: "Go to the demo" }).click(); - - await page.getByLabel("Query", { exact: true }).selectOption("syphilis"); - await page - .getByLabel("Patient", { exact: true }) - .selectOption("sti-syphilis-positive"); - await page.getByRole("button", { name: "Fill fields" }).click(); + // For some reason only in Chromium (ie not in firefox / webkit) there were + // issues connecting to the database for the cancer use case, which was resulting + // in errors on the results view screen when checking for the query result. + // Switching to chlymdia seemed to solve the issue, but leaving this check + // in just in case something similar happens in the future so the unlucky + // dev can have a note to help debug. - // Delete last name and MRN to force phone number as one of the 3 fields - await page.getByLabel("Last Name").clear(); - await page.getByLabel("Medical Record Number").clear(); - - // Among verification, make sure phone number is right - await page.getByRole("button", { name: "Search for patient" }).click(); - await expect(page.getByText("Loading")).toHaveCount(0, { timeout: 10000 }); await expect( - page.getByRole("heading", { name: "Patient Record" }), - ).toBeVisible(); - await expect(page.getByText("Patient Name")).toBeVisible(); - await expect(page.getByText("Hyper A. Unlucky")).toBeVisible(); - await expect(page.getByText("Contact")).toBeVisible(); - await expect(page.getByText("517-425-1398")).toBeVisible(); - await expect(page.getByText("Patient Identifiers")).toBeVisible(); - await expect(page.getByText("8692756")).toBeVisible(); - }); + page.getByText("0 labs found, 0 medications found, 0 conditions found."), + ).not.toBeVisible(); - test("social determinants query with generalized function", async ({ - page, - }) => { - await page.getByRole("button", { name: "Go to the demo" }).click(); - await page - .getByLabel("Query", { exact: true }) - .selectOption("social-determinants"); - await page.getByRole("button", { name: "Fill fields" }).click(); - await page.getByRole("button", { name: "Search for patient" }).click(); - await expect( - page.getByRole("heading", { name: "Patient Record" }), - ).toBeVisible(); - }); + await page.getByText("Return to patient search").click(); - test("form-fillable STI query using generalized function", async ({ - page, - }) => { - await page.getByRole("button", { name: "Go to the demo" }).click(); - await page.getByLabel("Query", { exact: true }).selectOption("chlamydia"); - await page.getByRole("button", { name: "Fill fields" }).click(); - await page.getByRole("button", { name: "Search for patient" }).click(); + await page.getByRole("button", { name: "Submit" }).click(); await expect(page.getByText("Loading")).toHaveCount(0, { timeout: 10000 }); - await expect( - page.getByRole("heading", { name: "Patient Record" }), - ).toBeVisible(); - }); -}); - -test.describe("Test the user journey of a 'tester'", () => { - test.beforeEach(async ({ page }) => { - // Start every test on direct tester page - await page.goto("http://localhost:3000/tefca-viewer/query/test", { - waitUntil: "load", - }); - }); - - test("query/test page loads", async ({ page }) => { - // Check that interactable elements are present - await expect( - page.getByRole("button", { name: "Data Usage Policy" }), - ).toBeVisible(); - await expect( - page.getByRole("link", { name: "TEFCA Viewer" }), - ).toBeVisible(); - - // Check that each expected text section is present - await expect( - page.getByRole("heading", { name: "Search for a Patient", exact: true }), - ).toBeVisible(); - await expect(page.getByLabel("Query", { exact: true })).toBeVisible(); - await expect(page.getByLabel("Patient", { exact: true })).toBeVisible(); - await expect(page.getByRole("button", { name: "Advanced" })).toBeVisible(); - }); - - test("Query for patient using auto-filled data", async ({ page }) => { - await page - .getByLabel("Query", { exact: true }) - .selectOption({ value: "newborn-screening" }); - await page - .getByLabel("Patient", { exact: true }) - .selectOption({ value: "newborn-screening-referral" }); - await page.getByRole("button", { name: "Fill fields" }).click(); - await page.getByRole("button", { name: "Search for patient" }).click(); - // Make sure we have a results page with a single patient + // Non-interactive 'div' elements in the table should be located by text await expect( page.getByRole("heading", { name: "Patient Record" }), ).toBeVisible(); await expect(page.getByText("Patient Name")).toBeVisible(); - await expect(page.getByText("WATERMELON SPROUT MCGEE")).toBeVisible(); + await expect(page.getByText(TEST_PATIENT_NAME)).toBeVisible(); await expect(page.getByText("Patient Identifiers")).toBeVisible(); - await expect(page.getByText("MRN: 18091")).toBeVisible(); - }); - - test("Query for patient by filling in data", async ({ page }) => { - await page - .getByLabel("Query", { exact: true }) - .selectOption("Newborn screening follow-up"); - await page.getByRole("button", { name: "Advanced" }).click(); - await page - .getByLabel("FHIR Server (QHIN)", { exact: true }) - .selectOption("HELIOS Meld: Direct"); - await page.getByLabel("First Name").fill("Watermelon"); - await page.getByLabel("Last Name").fill("McGee"); - await page.getByLabel("Phone Number").fill("5555555555"); - await page.getByLabel("Date of Birth").fill("2024-07-12"); - await page.getByLabel("Medical Record Number").fill("18091"); - - await page.getByRole("button", { name: "Search for patient" }).click(); - - // Make sure we have a results page with a single patient await expect( - page.getByRole("heading", { name: "Patient Record" }), + page.getByText(`Medical Record Number: ${TEST_PATIENT.MRN}`), ).toBeVisible(); - await expect(page.getByText("Patient Name")).toBeVisible(); - await expect(page.getByText("WATERMELON SPROUT MCGEE")).toBeVisible(); - await expect(page.getByText("Patient Identifiers")).toBeVisible(); - await expect(page.getByText("MRN: 18091")).toBeVisible(); - }); - - test("Query with multiple patients returned", async ({ page }) => { - // Query for a patient with multiple results - await page - .getByLabel("Query", { exact: true }) - .selectOption("Chlamydia case investigation"); - await page.getByRole("button", { name: "Advanced" }).click(); - await page - .getByLabel("FHIR Server (QHIN)", { exact: true }) - .selectOption("JMC Meld: Direct"); - await page.getByLabel("Last Name").fill("JMC"); - await page.getByRole("button", { name: "Search for patient" }).click(); - // Make sure all the elements for the multiple patients view appear - await expect( - page.getByRole("heading", { name: "Select a patient" }), - ).toBeVisible(); - // Check that there is a Table element with the correct headers - await expect(page.locator("thead").locator("tr")).toHaveText( - "NameDOBContactAddressMRNActions", + // Check that the info alert is visible and has updated to the correct text + const alert2 = page.locator(".custom-alert"); + await expect(alert2).toBeVisible(); + await expect(alert2).toHaveText( + `${CONTACT_US_DISCLAIMER_TEXT} ${CONTACT_US_DISCLAIMER_EMAIL}`, ); - // Check that there are multiple rows in the table - await expect(page.locator("tbody").locator("tr")).toHaveCount(10); - - // Click on the first patient's "Select patient" button - await page.locator(':nth-match(:text("Select patient"), 1)').click(); - - // Make sure we have a results page with a single patient & appropriate back buttons await expect( - page.getByRole("heading", { name: "Patient Record" }), + page.getByRole("button", { name: "Observations", expanded: true }), ).toBeVisible(); await expect( - page.getByRole("button", { name: "New patient search" }), + page.getByRole("button", { name: "Medication Requests", expanded: true }), ).toBeVisible(); + // We can also just directly ask the page to find us filtered table rows + await expect(page.locator("tbody").locator("tr")).toHaveCount(32); + + // Now let's use the return to search to go back to a blank form await page.getByRole("button", { name: "New patient search" }).click(); await expect( - page.getByRole("heading", { name: "Search for a Patient", exact: true }), + page.getByRole("heading", { name: STEP_ONE_PAGE_TITLE, exact: true }), ).toBeVisible(); }); }); diff --git a/query-connector/src/app/CustomQuery.ts b/query-connector/src/app/CustomQuery.ts index beb1396cb..3f72897bb 100644 --- a/query-connector/src/app/CustomQuery.ts +++ b/query-connector/src/app/CustomQuery.ts @@ -75,7 +75,7 @@ export class CustomQuery { : ""; this.medicationRequestQuery = rxnormFilter !== "" - ? `/MedicationRequest?subject=${patientId}&code=${rxnormFilter}&_include=MedicationRequest:medication&_include=MedicationRequest:medication.administration` + ? `/MedicationRequest?subject=${patientId}&code=${rxnormFilter}&_include=MedicationRequest:medication&_include=MedicationRequest:intended-performer` : ""; this.socialHistoryQuery = `/Observation?subject=${patientId}&category=social-history`; this.encounterQuery = diff --git a/query-connector/src/app/constants.ts b/query-connector/src/app/constants.ts index d61ed2410..46ae1880e 100644 --- a/query-connector/src/app/constants.ts +++ b/query-connector/src/app/constants.ts @@ -37,6 +37,14 @@ export const demoQueryOptions = [ { 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; + }, + {} as Record, +); /* * 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. @@ -90,16 +98,17 @@ export type PatientType = | "social-determinants" | "sti-syphilis-positive"; +export const DEFAULT_DEMO_FHIR_SERVER = "Public HAPI: Direct"; /* * Common "Hyper Unlucky" patient data used for all non-newborn screening use cases */ -const hyperUnluckyPatient: DemoDataFields = { +export const hyperUnluckyPatient: DemoDataFields = { FirstName: "Hyper", LastName: "Unlucky", DOB: "1975-12-06", MRN: "8692756", Phone: "517-425-1398", - FhirServer: "Public HAPI: Direct", + FhirServer: DEFAULT_DEMO_FHIR_SERVER, UseCase: "cancer", // UseCase will be updated per case }; @@ -276,12 +285,7 @@ export const stateOptions = [ ]; /* Mode that pages can be in; determines what is displayed to the user */ -export type Mode = - | "search" - | "results" - | "customize-queries" - | "select-query" - | "patient-results"; +export type Mode = "search" | "results" | "select-query" | "patient-results"; /*Type to specify the expected components for each item in a value set that will be displayed in the CustomizeQuery component*/ @@ -310,3 +314,8 @@ export const valueSetTypeToClincalServiceTypeMap = { medications: ["mrtc"], conditions: ["dxtc", "sdtc"], }; + +export const metadata = { + title: "Query Connector", + description: "Try out TEFCA with queries for public health use cases.", +}; diff --git a/query-connector/src/app/layout.tsx b/query-connector/src/app/layout.tsx index 97ab8293f..e360e1dca 100644 --- a/query-connector/src/app/layout.tsx +++ b/query-connector/src/app/layout.tsx @@ -3,11 +3,6 @@ import Header from "./query/components/header/header"; import Footer from "./footer"; import { DataProvider } from "./utils"; -export const metadata = { - title: "TEFCA Viewer", - description: "Try out TEFCA with queries for public health use cases.", -}; - /** * Establishes the layout for the application. * @param props - Props for the component. diff --git a/query-connector/src/app/query/components/CustomizeQuery.tsx b/query-connector/src/app/query/components/CustomizeQuery.tsx index 7e4e62a78..a8a6f4770 100644 --- a/query-connector/src/app/query/components/CustomizeQuery.tsx +++ b/query-connector/src/app/query/components/CustomizeQuery.tsx @@ -2,7 +2,12 @@ import React, { useState, useEffect } from "react"; import { Button } from "@trussworks/react-uswds"; -import { ValueSetType, ValueSetItem } from "../../constants"; +import { + ValueSetType, + ValueSetItem, + USE_CASES, + demoQueryValToLabelMap, +} from "../../constants"; import { UseCaseQueryResponse } from "@/app/query-service"; import LoadingView from "./LoadingView"; import { showRedirectConfirmation } from "../designSystem/redirectToast/RedirectToast"; @@ -16,7 +21,7 @@ import Backlink from "./backLink/Backlink"; interface CustomizeQueryProps { useCaseQueryResponse: UseCaseQueryResponse; - queryType: string; + queryType: USE_CASES; queryValuesets: ValueSetItem[]; setQueryValuesets: (queryVS: ValueSetItem[]) => void; goBack: () => void; @@ -125,11 +130,7 @@ const CustomizeQuery: React.FC = ({ const handleApplyChanges = () => { const selectedItems = Object.keys(valueSetOptions).reduce((acc, key) => { const items = valueSetOptions[key as ValueSetType]; - acc = acc.concat( - Object.values(items) - .flatMap((dict) => dict.items) - .filter((item) => item.include), - ); + acc = acc.concat(Object.values(items).flatMap((dict) => dict.items)); return acc; }, [] as ValueSetItem[]); setQueryValuesets(selectedItems); @@ -165,7 +166,7 @@ const CustomizeQuery: React.FC = ({ Customize query
- Query: {queryType} + Query: {demoQueryValToLabelMap[queryType]}
{countLabs} labs found, {countMedications} medications found,{" "} diff --git a/query-connector/src/app/query/components/PatientSearchResults.tsx b/query-connector/src/app/query/components/PatientSearchResults.tsx index ee41abc7f..5d01f6e76 100644 --- a/query-connector/src/app/query/components/PatientSearchResults.tsx +++ b/query-connector/src/app/query/components/PatientSearchResults.tsx @@ -1,12 +1,7 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect } from "react"; import { Patient } from "fhir/r4"; -import { - UseCaseQueryResponse, - UseCaseQuery, - UseCaseQueryRequest, -} from "../../query-service"; -import { Mode, ValueSetItem } from "@/app/constants"; +import { Mode } from "@/app/constants"; import Backlink from "./backLink/Backlink"; import PatientSearchResultsTable from "./patientSearchResults/PatientSearchResultsTable"; import NoPatientsFound from "./patientSearchResults/NoPatientsFound"; @@ -16,94 +11,58 @@ import NoPatientsFound from "./patientSearchResults/NoPatientsFound"; */ export interface PatientSearchResultsProps { patients: Patient[]; - originalRequest: UseCaseQueryRequest; - queryValueSets: ValueSetItem[]; - setLoading: (loading: boolean) => void; goBack: () => void; setMode: (mode: Mode) => void; - setUseCaseQueryResponse: (UseCaseQueryResponse: UseCaseQueryResponse) => void; + setPatientForQueryResponse: (patient: Patient) => void; } /** * Displays multiple patient search results in a table. * @param root0 - PatientSearchResults props. * @param root0.patients - The array of Patient resources. - * @param root0.originalRequest - The original request object. - * @param root0.queryValueSets - The stateful collection of value sets to include - * in the query. - * @param root0.setLoading - The function to set the loading state. * @param root0.goBack - The function to go back to the previous page. - * @param root0.setUseCaseQueryResponse - State update function to pass the - * data needed for the results view back up to the parent component * @param root0.setMode - Redirect function to handle results view routing + * @param root0.setPatientForQueryResponse - Callback function to update the + * patient being searched for * @returns - The PatientSearchResults component. */ const PatientSearchResults: React.FC = ({ patients, - originalRequest, - queryValueSets, - setLoading, goBack, - setUseCaseQueryResponse, + setPatientForQueryResponse, setMode, }) => { useEffect(() => { window.scrollTo(0, 0); }, []); - const [patientForQuery, setPatientForQueryResponse] = useState(); - useEffect(() => { - let isSubscribed = true; - - const fetchQuery = async () => { - if (patientForQuery && isSubscribed) { - setLoading(true); - const queryResponse = await UseCaseQuery( - originalRequest, - queryValueSets, - { - Patient: [patientForQuery], - }, - ); - setUseCaseQueryResponse(queryResponse); - setMode("results"); - setLoading(false); - } - }; - - fetchQuery().catch(console.error); - - // Destructor hook to prevent future state updates - return () => { - isSubscribed = false; - }; - }, [patientForQuery]); + function handlePatientSelect(patient: Patient) { + setPatientForQueryResponse(patient); + setMode("select-query"); + } return ( <> - {patients.length === 0 && ( <> - + Revise your patient search )} {patients.length > 0 && ( <> + + - -

- Not seeing what you're looking for? -

- - - Return to patient search - )} diff --git a/query-connector/src/app/query/components/ResultsView.tsx b/query-connector/src/app/query/components/ResultsView.tsx index 23ad78470..ac1debfc6 100644 --- a/query-connector/src/app/query/components/ResultsView.tsx +++ b/query-connector/src/app/query/components/ResultsView.tsx @@ -11,11 +11,14 @@ import DiagnosticReportTable from "./resultsView/tableComponents/DiagnosticRepor 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, UseCaseToQueryName } from "@/app/constants"; type ResultsViewProps = { useCaseQueryResponse: UseCaseQueryResponse; + selectedQuery: USE_CASES; goBack: () => void; - queryName: string; + goToBeginning: () => void; }; export type ResultsViewAccordionItem = { @@ -29,13 +32,15 @@ export type ResultsViewAccordionItem = { * @param props - The props for the QueryView component. * @param props.useCaseQueryResponse - The response from the query service. * @param props.goBack - The function to go back to the previous page. - * @param props.queryName - The name of the saved query to display to the user + * @param props.goToBeginning - Function to return to patient discover + * @param props.selectedQuery - query that's been selected to view for results * @returns The QueryView component. */ const ResultsView: React.FC = ({ useCaseQueryResponse, + selectedQuery, goBack, - queryName, + goToBeginning, }) => { useEffect(() => { window.scrollTo(0, 0); @@ -56,9 +61,13 @@ const ResultsView: React.FC = ({ <>
+ goBack()} + label={"Return to query selection"} + /> @@ -70,7 +79,9 @@ const ResultsView: React.FC = ({

Query:{" "} - {queryName} + + {UseCaseToQueryName[selectedQuery]} +

diff --git a/query-connector/src/app/query/components/header/header.tsx b/query-connector/src/app/query/components/header/header.tsx index b36e3017e..5667dfab3 100644 --- a/query-connector/src/app/query/components/header/header.tsx +++ b/query-connector/src/app/query/components/header/header.tsx @@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from "react"; import { Modal, ModalButton } from "../../designSystem/Modal"; import { ModalRef } from "@trussworks/react-uswds"; import styles from "./header.module.css"; +import { metadata } from "@/app/constants"; /** * Produces the header. * @returns The HeaderComponent component. @@ -33,9 +34,9 @@ export default function HeaderComponent() { - TEFCA Viewer + {metadata.title}
diff --git a/query-connector/src/app/query/components/patientSearchResults/PatientSearchResultsTable.tsx b/query-connector/src/app/query/components/patientSearchResults/PatientSearchResultsTable.tsx index 53fabf08b..59bdf38b7 100644 --- a/query-connector/src/app/query/components/patientSearchResults/PatientSearchResultsTable.tsx +++ b/query-connector/src/app/query/components/patientSearchResults/PatientSearchResultsTable.tsx @@ -9,7 +9,7 @@ import { type PatientSeacrchResultsTableProps = { patients: Patient[]; - setPatientForQueryResponse: (patient: Patient) => void; + handlePatientSelect: (patient: Patient) => void; }; /** @@ -17,18 +17,18 @@ type PatientSeacrchResultsTableProps = { * include in their query * @param param0 - props * @param param0.patients - Patient[] from the FHIR spec to display as rows - * @param param0.setPatientForQueryResponse - state setter function to redirect + * @param param0.handlePatientSelect - state setter function to redirect * to the results view * @returns The patient search results view */ const PatientSearchResultsTable: React.FC = ({ patients, - setPatientForQueryResponse, + handlePatientSelect: setPatientForQueryResponse, }) => { return ( <>

- Select a patient + {STEP_TWO_PAGE_TITLE}

The following records match your search. Select a patient to continue. @@ -74,3 +74,4 @@ const PatientSearchResultsTable: React.FC = ({ }; export default PatientSearchResultsTable; +export const STEP_TWO_PAGE_TITLE = "Step 2: Select a patient"; diff --git a/query-connector/src/app/query/components/resultsView/ResultsViewTable.tsx b/query-connector/src/app/query/components/resultsView/ResultsViewTable.tsx index 5481ba517..b828b46c3 100644 --- a/query-connector/src/app/query/components/resultsView/ResultsViewTable.tsx +++ b/query-connector/src/app/query/components/resultsView/ResultsViewTable.tsx @@ -57,5 +57,5 @@ export default ResultsViewTable; * @returns - A hyphenated id that can be linked as an anchor tag */ export function formatIdForAnchorTag(title: string) { - return title.toLocaleLowerCase().replace(" ", "-"); + return title?.toLocaleLowerCase().replace(" ", "-"); } diff --git a/query-connector/src/app/query/components/searchForm/SearchForm.tsx b/query-connector/src/app/query/components/searchForm/SearchForm.tsx index aedac810e..cc12ab403 100644 --- a/query-connector/src/app/query/components/searchForm/SearchForm.tsx +++ b/query-connector/src/app/query/components/searchForm/SearchForm.tsx @@ -10,18 +10,10 @@ import { USE_CASES, FHIR_SERVERS, demoData, - PatientType, - demoQueryOptions, - patientOptions, stateOptions, Mode, - ValueSetItem, } from "../../../constants"; -import { - UseCaseQueryResponse, - UseCaseQuery, - UseCaseQueryRequest, -} from "../../../query-service"; +import { UseCaseQueryResponse, UseCaseQuery } from "../../../query-service"; import { fhirServers } from "../../../fhir-servers"; import styles from "./searchForm.module.css"; @@ -29,45 +21,41 @@ import { FormatPhoneAsDigits } from "@/app/format-service"; interface SearchFormProps { useCase: USE_CASES; - queryValueSets: ValueSetItem[]; setUseCase: (useCase: USE_CASES) => void; - setOriginalRequest: (originalRequest: UseCaseQueryRequest) => void; - setUseCaseQueryResponse: (UseCaseQueryResponse: UseCaseQueryResponse) => void; + setPatientDiscoveryQueryResponse: ( + UseCaseQueryResponse: UseCaseQueryResponse, + ) => void; setMode: (mode: Mode) => void; setLoading: (loading: boolean) => void; - setQueryType: (queryType: string) => void; + fhirServer: FHIR_SERVERS; + setFhirServer: React.Dispatch>; } /** * @param root0 - SearchFormProps * @param root0.useCase - The use case this query will cover. - * @param root0.queryValueSets - Stateful collection of valuesets to use in the query. * @param root0.setUseCase - Update stateful use case. - * @param root0.setOriginalRequest - The function to set the original request. - * @param root0.setUseCaseQueryResponse - The function to set the use case query response. * @param root0.setMode - The function to set the mode. * @param root0.setLoading - The function to set the loading state. - * @param root0.setQueryType - The function to set the query type. + * @param root0.setPatientDiscoveryQueryResponse - callback function to set the + * patient for use in future steps + * @param root0.fhirServer - server to do the query against + * @param root0.setFhirServer - callback function to update specified query * @returns - The SearchForm component. */ const SearchForm: React.FC = ({ useCase, - queryValueSets, setUseCase, - setOriginalRequest, - setUseCaseQueryResponse, + setPatientDiscoveryQueryResponse, setMode, setLoading, - setQueryType, + fhirServer, + setFhirServer, }) => { //Set the patient options based on the demoOption - const [patientOption, setPatientOption] = useState( - patientOptions[useCase]?.[0]?.value || "", - ); const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); - const [fhirServer, setFhirServer] = useState(); - // "Public HAPI: Direct", + const [phone, setPhone] = useState(""); const [dob, setDOB] = useState(""); const [mrn, setMRN] = useState(""); @@ -75,10 +63,10 @@ const SearchForm: React.FC = ({ const [showAdvanced, setShowAdvanced] = useState(false); const [autofilled, setAutofilled] = useState(false); // boolean indicating if the form was autofilled, changes color if true - // Fills fields with sample data based on the selected patientOption + // Fills fields with sample data based on the selected const fillFields = useCallback( - (patientOption: PatientType, highlightAutofilled = true) => { - const data = demoData[patientOption]; + (highlightAutofilled = true) => { + const data = demoData["cancer"]; if (data) { setFirstName(data.FirstName); setLastName(data.LastName); @@ -90,23 +78,9 @@ const SearchForm: React.FC = ({ setAutofilled(highlightAutofilled); } }, - [patientOption, setUseCase, setQueryType], + [setUseCase], ); - // Change the selectedDemoOption in the dropdown and update the - // query type (which governs the DB fetch) accordingly - const handleDemoQueryChange = (selectedDemoOption: string) => { - setPatientOption(patientOptions[selectedDemoOption][0].value); - setQueryType( - demoQueryOptions.find((dqo) => dqo.value == selectedDemoOption)?.label || - "", - ); - }; - - const handleClick = () => { - setMode("customize-queries"); - }; - async function HandleSubmit(event: React.FormEvent) { event.preventDefault(); if (!useCase || !fhirServer) { @@ -124,15 +98,10 @@ const SearchForm: React.FC = ({ use_case: useCase, phone: FormatPhoneAsDigits(phone), }; - setOriginalRequest(originalRequest); - const queryResponse = await UseCaseQuery(originalRequest, queryValueSets); - setUseCaseQueryResponse(queryResponse); + const queryResponse = await UseCaseQuery(originalRequest, []); + setPatientDiscoveryQueryResponse(queryResponse); - if (queryResponse.Patient && queryResponse.Patient.length === 1) { - setMode("results"); - } else { - setMode("patient-results"); - } + setMode("patient-results"); setLoading(false); } useEffect(() => { @@ -143,7 +112,7 @@ const SearchForm: React.FC = ({ <>

- Search for a Patient + {STEP_ONE_PAGE_TITLE}

Enter patient information below to search for a patient. We will query @@ -156,76 +125,22 @@ const SearchForm: React.FC = ({ htmlFor="query" > The demo site uses synthetic data to provide examples of possible - queries that you can make with the TEFCA Viewer. Select a query + queries that you can make with the Query Connector. Select a query use case, a sample patient, and then click “fill fields” below. -
-
- -
- -
-
-
- -
- -
-
-
-

} -
+
{showAdvanced && ( -
-