From 14c28da35e298011c42bcf8347a715ea10309bc4 Mon Sep 17 00:00:00 2001 From: Daniel Paseltiner <99684231+DanPaseltiner@users.noreply.github.com> Date: Fri, 3 May 2024 11:28:01 -0400 Subject: [PATCH] TEFCA Viewer Conditions table (#1764) * Import CodeableConcept in format-service * Remove additional eCR carryover files * Update next.config.js for tefca-viewer * Allow for self-signed certifcates * Condtions table working implementation * Exploring to address build issues --- containers/tefca-viewer/.env | 5 --- containers/tefca-viewer/api-documentation.md | 43 ------------------ containers/tefca-viewer/next.config.js | 4 +- containers/tefca-viewer/package.json | 9 ++-- .../tefca-viewer/src/app/format-service.tsx | 21 +++++++++ .../tefca-viewer/src/app/query-service.ts | 11 ++--- .../query/components/AccordionContainer.tsx | 23 ++++++++++ .../app/query/components/ConditionsTable.tsx | 44 +++++++++++++++++++ .../app/query/components/ObservationTable.tsx | 24 +--------- .../src/styles/custom-styles.scss | 4 ++ .../src/styles/uswds-settings.scss | 23 ++++++++++ 11 files changed, 129 insertions(+), 82 deletions(-) delete mode 100644 containers/tefca-viewer/.env delete mode 100644 containers/tefca-viewer/api-documentation.md create mode 100644 containers/tefca-viewer/src/app/query/components/ConditionsTable.tsx diff --git a/containers/tefca-viewer/.env b/containers/tefca-viewer/.env deleted file mode 100644 index 545d8e166e..0000000000 --- a/containers/tefca-viewer/.env +++ /dev/null @@ -1,5 +0,0 @@ -DATABASE_URL=postgres://postgres:pw@localhost:5432/ecr_viewer_db -AWS_REGION=us-east-1 -ECR_BUCKET_NAME=ecr-viewer-files -SOURCE=postgres -NEXT_TELEMETRY_DISABLED=1 \ No newline at end of file diff --git a/containers/tefca-viewer/api-documentation.md b/containers/tefca-viewer/api-documentation.md deleted file mode 100644 index 5e1d5daa1f..0000000000 --- a/containers/tefca-viewer/api-documentation.md +++ /dev/null @@ -1,43 +0,0 @@ -# View eCR - -Display an eCR - -**URL** : `/view-data?id=:id&snomed-code=:snomed&auth=:auth` - -**URL Parameters** : -- `id=[string]` where `id` is the ID of the eCR. -- `snomed-code=[string]` where `snomed-code` is the condition the user is viewing the eCR for. -- `auth=[string]` where `auth` is the authentication token for the user - -**Method** : `GET` - -**Auth required** : YES - -**Permissions required** : None - -## Example Architecture -![NBS -> ECR Viewer sequence diagram](assets/nbs-ecr-viewer-arch.png) - -## Success Response - -**Condition** : eCR exists and authentication is valid. - -**Code** : `200 OK` - -**Content** : eCR will be displayed to the user - -## Error Responses - -**Condition** : eCR does not exist with `id` - -**Code** : `404 NOT FOUND` - -**Content** : Error will be displayed to user - -### Or - -**Condition** : Authentication is invalid - -**Code** : `401 UNAUTHORIZED` - -**Content** : Error will be displayed to user diff --git a/containers/tefca-viewer/next.config.js b/containers/tefca-viewer/next.config.js index b0e2f53d73..646f7373ca 100644 --- a/containers/tefca-viewer/next.config.js +++ b/containers/tefca-viewer/next.config.js @@ -11,13 +11,13 @@ const nextConfig = { async rewrites() { return [ { - source: "/ecr-viewer/:slug*", + source: "/tefca-viewer/:slug*", destination: "/:slug*", }, ]; }, output: "standalone", - basePath: process.env.NODE_ENV === "production" ? "/ecr-viewer" : "", + basePath: process.env.NODE_ENV === "production" ? "/tefca-viewer" : "", }; module.exports = nextConfig; diff --git a/containers/tefca-viewer/package.json b/containers/tefca-viewer/package.json index c9469e8848..f53f0e7f07 100644 --- a/containers/tefca-viewer/package.json +++ b/containers/tefca-viewer/package.json @@ -1,14 +1,14 @@ { "name": "tefca-viewer", - "description": "The DIBBs eCR Viewer app offers a REST API for processing FHIR messages into an HTML page with key insights.", + "description": "The DIBBs TryTEFCA Viewer is a FHIR client for public health specific queries.", "version": "1.0.1", "private": true, "scripts": { - "dev": "next dev --experimental-https", + "dev": "NODE_TLS_REJECT_UNAUTHORIZED=0 next dev", "local-dev": "npm run setup-local-env && docker compose up db seed-db -d && docker-compose logs && export DATABASE_URL=postgres://postgres:pw@localhost:5432/ecr_viewer_db && npm run dev", "setup-local-env": "./setup-env.sh", - "build": "next build", - "start": "next start", + "build": "NODE_TLS_REJECT_UNAUTHORIZED=0 next build", + "start": "NODE_TLS_REJECT_UNAUTHORIZED=0 next start", "lint": "next lint", "test": "npm run test:unit && npm run test:integration", "test:unit": "TZ=America/New_York jest", @@ -54,7 +54,6 @@ "autoprefixer": "^10.0.1", "aws-sdk-client-mock": "^3.0.1", "chai": "^4.3.10", - "cypress": "^13.6.6", "eslint": "^8.56.0", "eslint-config-next": "14.0.3", "eslint-config-prettier": "^9.1.0", diff --git a/containers/tefca-viewer/src/app/format-service.tsx b/containers/tefca-viewer/src/app/format-service.tsx index 1e5fcc9671..18bd294c2e 100644 --- a/containers/tefca-viewer/src/app/format-service.tsx +++ b/containers/tefca-viewer/src/app/format-service.tsx @@ -1,3 +1,4 @@ +import { CodeableConcept } from "fhir/r4"; /** * Formats a string. * @param input - The string to format. @@ -15,3 +16,23 @@ export const formatString = (input: string): string => { return result; }; + +/** + * Formats a CodeableConcept object for display. If the object has a coding array, + * the first coding object is used. + * @param concept - The CodeableConcept object. + * @returns The CodeableConcept data formatted for + * display. + */ +export function formatCodeableConcept(concept: CodeableConcept) { + if (!concept.coding || concept.coding.length === 0) { + return concept.text || ""; + } + const coding = concept.coding[0]; + return ( + <> + {" "} + {coding.display}
{coding.code}
{coding.system}{" "} + + ); +} diff --git a/containers/tefca-viewer/src/app/query-service.ts b/containers/tefca-viewer/src/app/query-service.ts index b86bc039e0..87972e778d 100644 --- a/containers/tefca-viewer/src/app/query-service.ts +++ b/containers/tefca-viewer/src/app/query-service.ts @@ -32,14 +32,12 @@ const FHIR_SERVERS: { meld: { hostname: "https://gw.interop.community/HeliosConnectathonSa/open/" }, ehealthexchange: { hostname: "https://concept01.ehealthexchange.org:52780/fhirproxy/r4/", - username: "svc_eHxFHIRSandbox", - password: "willfulStrongStandurd7", headers: { Accept: "application/json, application/*+json, */*", "Accept-Encoding": "gzip, deflate, br", "Content-Type": "application/fhir+json; charset=UTF-8", "X-DESTINATION": "CernerHelios", - "X-POU": "TREATMENT", + "X-POU": "PUBHLTH", "X-Request-Id": uuidv4(), prefer: "return=representation", "Cache-Control": "no-cache", @@ -224,8 +222,11 @@ async function newbornScreeningQuery( ]; const loincFilter: string = "code=" + loincs.join(","); - const query = `/Observation?subject=${request.patientId}&code=${loincFilter}`; - const response = await fetch(request.fhir_host + query, request.init); + const query = `/Observation?subject=Patient/${request.patientId}&code=${loincFilter}`; + const response = await fetch(request.fhir_host + query, { + headers: request.headers, + ...request.init, + }); if (response.status !== 200) { console.log("response:", response); diff --git a/containers/tefca-viewer/src/app/query/components/AccordionContainer.tsx b/containers/tefca-viewer/src/app/query/components/AccordionContainer.tsx index c0456c0f9a..a3dc7b7e06 100644 --- a/containers/tefca-viewer/src/app/query/components/AccordionContainer.tsx +++ b/containers/tefca-viewer/src/app/query/components/AccordionContainer.tsx @@ -9,6 +9,7 @@ import { AccordianDiv, } from "../component-utils"; import { UseCaseQueryResponse } from "@/app/query-service"; +import ConditionsTable from "./ConditionsTable"; type AccordionContainerProps = { queryResponse: UseCaseQueryResponse; @@ -23,6 +24,7 @@ type AccordionContainerProps = { const AccordianContainer: React.FC = ({ queryResponse, }) => { + console.log("queryResponse:", queryResponse); const accordionItems: any[] = []; const patient = @@ -32,6 +34,7 @@ const AccordianContainer: React.FC = ({ const observations = queryResponse.observations ? queryResponse.observations : null; + const conditions = queryResponse.conditions ? queryResponse.conditions : null; if (patient) { accordionItems.push({ @@ -73,6 +76,26 @@ const AccordianContainer: React.FC = ({ }); } + if (conditions) { + accordionItems.push({ + title: "Conditions", + content: ( + <> + + + Conditions + + + + + + + ), + expanded: true, + headingLevel: "h3", + }); + } + //Add id, adjust title accordionItems.forEach((item, index) => { let formattedTitle = formatString(item["title"]); diff --git a/containers/tefca-viewer/src/app/query/components/ConditionsTable.tsx b/containers/tefca-viewer/src/app/query/components/ConditionsTable.tsx new file mode 100644 index 0000000000..b5c9dde865 --- /dev/null +++ b/containers/tefca-viewer/src/app/query/components/ConditionsTable.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import { Table } from "@trussworks/react-uswds"; +import { Condition } from "fhir/r4"; +import { formatCodeableConcept } from "../../format-service"; + +/** + * The props for the ConditionTable component. + */ +export interface ConditionTableProps { + conditions: Condition[]; +} + +/** + * Displays a table of data from array of Condition resources. + * @param props - Condition table props. + * @param props.conditions - The array of Condition resources. + * @returns - The ConditionTable component. + */ +const ConditionsTable: React.FC = ({ conditions }) => { + return ( + + + + + + + + + + + {conditions.map((condition) => ( + + + + + + + ))} + +
ConditionStatusOnsetResolution
{formatCodeableConcept(condition.code ?? {})}{formatCodeableConcept(condition.clinicalStatus ?? {})}{condition.onsetDateTime}{condition.abatementDateTime}
+ ); +}; + +export default ConditionsTable; diff --git a/containers/tefca-viewer/src/app/query/components/ObservationTable.tsx b/containers/tefca-viewer/src/app/query/components/ObservationTable.tsx index 79c5da8dd3..01673a9ea0 100644 --- a/containers/tefca-viewer/src/app/query/components/ObservationTable.tsx +++ b/containers/tefca-viewer/src/app/query/components/ObservationTable.tsx @@ -1,7 +1,7 @@ import React from "react"; import { Table } from "@trussworks/react-uswds"; -import { Observation, CodeableConcept } from "fhir/r4"; - +import { Observation } from "fhir/r4"; +import { formatCodeableConcept } from "../../format-service"; /** * The props for the ObservationTable component. */ @@ -49,26 +49,6 @@ const ObservationTable: React.FC = ({ }; export default ObservationTable; -/** - * Formats a CodeableConcept object for display. If the object has a coding array, - * the first coding object is used. - * @param concept - The CodeableConcept object. - * @returns The CodeableConcept data formatted for - * display. - */ -function formatCodeableConcept(concept: CodeableConcept) { - if (!concept.coding || concept.coding.length === 0) { - return concept.text || ""; - } - const coding = concept.coding[0]; - return ( - <> - {" "} - {coding.display}
{coding.code}
{coding.system}{" "} - - ); -} - /** * Formats the value of an Observation object for display. * @param obs - The Observation object. diff --git a/containers/tefca-viewer/src/styles/custom-styles.scss b/containers/tefca-viewer/src/styles/custom-styles.scss index 07a8426351..09efbcceb7 100644 --- a/containers/tefca-viewer/src/styles/custom-styles.scss +++ b/containers/tefca-viewer/src/styles/custom-styles.scss @@ -268,4 +268,8 @@ body { margin-top: 2.0rem; margin-bottom: 4.0rem; max-width: 11em; +} + +.usa-table { + width: 100%; } \ No newline at end of file diff --git a/containers/tefca-viewer/src/styles/uswds-settings.scss b/containers/tefca-viewer/src/styles/uswds-settings.scss index e0ea3d76d5..cb71b536be 100644 --- a/containers/tefca-viewer/src/styles/uswds-settings.scss +++ b/containers/tefca-viewer/src/styles/uswds-settings.scss @@ -17,3 +17,26 @@ $ASSET_PREFIX: ""; $theme-hero-image: "#{$ASSET_PREFIX}/uswds/img/hero.webp", $utilities-use-important: true, ); + +/* https://designsystem.digital.gov/documentation/settings/ */ +// @use "uswds-core" with ( +// // General +// $theme-show-compile-warnings: true, +// $theme-show-notifications: false, +// $theme-image-path: "../../../../../../dist/img", + +// // Color + +// // Component +// $theme-hero-image: "../../../../dist/img/hero.jpg", +// $theme-accordion-button-background-color: 'blue-cool-60', + +// // Spacing + +// // Typography +// $theme-font-path: "../../../../../../dist/fonts", + + +// // Utilities + +// );