From 744ea3ff1733586fa7beb2c5125caa8a6c608d35 Mon Sep 17 00:00:00 2001 From: Mary McGrath Date: Mon, 16 Dec 2024 08:30:52 -0500 Subject: [PATCH 1/7] fix: missing specimen sources (#3044) * fix: update docker, snapshots * test: snapshots again * Update containers/fhir-converter/Dockerfile --- containers/fhir-converter/Dockerfile | 2 +- .../__snapshots__/test_FHIR-Converter.ambr | 204 ++++++++++++++++++ 2 files changed, 205 insertions(+), 1 deletion(-) diff --git a/containers/fhir-converter/Dockerfile b/containers/fhir-converter/Dockerfile index 032523e7c..e234fd472 100644 --- a/containers/fhir-converter/Dockerfile +++ b/containers/fhir-converter/Dockerfile @@ -1,7 +1,7 @@ FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build # Download FHIR-Converter -RUN git clone https://github.com/skylight-hq/FHIR-Converter.git --branch v7.0-skylight-16 --single-branch /build/FHIR-Converter +RUN git clone https://github.com/skylight-hq/FHIR-Converter.git --branch v7.0-skylight-17 --single-branch /build/FHIR-Converter WORKDIR /build/FHIR-Converter diff --git a/containers/fhir-converter/tests/integration/__snapshots__/test_FHIR-Converter.ambr b/containers/fhir-converter/tests/integration/__snapshots__/test_FHIR-Converter.ambr index 4b4fe3127..a93825bdd 100644 --- a/containers/fhir-converter/tests/integration/__snapshots__/test_FHIR-Converter.ambr +++ b/containers/fhir-converter/tests/integration/__snapshots__/test_FHIR-Converter.ambr @@ -2792,6 +2792,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -2916,6 +2920,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -3019,6 +3027,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -3092,6 +3104,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -3193,6 +3209,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -3278,6 +3298,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -3358,6 +3382,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -3434,6 +3462,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -3510,6 +3542,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -3603,6 +3639,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -3683,6 +3723,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -3763,6 +3807,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -3852,6 +3900,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -3956,6 +4008,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -4049,6 +4105,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -4138,6 +4198,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -4192,6 +4256,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T16:48:00Z', @@ -4319,6 +4387,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:31:00Z', @@ -4416,6 +4488,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:31:00Z', @@ -4513,6 +4589,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:31:00Z', @@ -4610,6 +4690,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:31:00Z', @@ -4702,6 +4786,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:31:00Z', @@ -4802,6 +4890,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:31:00Z', @@ -4856,6 +4948,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:31:00Z', @@ -4992,6 +5088,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:44:00Z', @@ -5093,6 +5193,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:44:00Z', @@ -5193,6 +5297,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:44:00Z', @@ -5290,6 +5398,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:44:00Z', @@ -5379,6 +5491,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:44:00Z', @@ -5472,6 +5588,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:44:00Z', @@ -5569,6 +5689,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:44:00Z', @@ -5658,6 +5782,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:44:00Z', @@ -5747,6 +5875,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:44:00Z', @@ -5801,6 +5933,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:44:00Z', @@ -5916,6 +6052,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:53:00Z', @@ -5994,6 +6134,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:53:00Z', @@ -6048,6 +6192,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Blood', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T17:53:00Z', @@ -6173,6 +6321,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T19:18:00Z', @@ -6266,6 +6418,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T19:18:00Z', @@ -6357,6 +6513,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T19:18:00Z', @@ -6457,6 +6617,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T19:18:00Z', @@ -6511,6 +6675,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Semen', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T19:18:00Z', @@ -6626,6 +6794,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Urine', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T20:22:00Z', @@ -6723,6 +6895,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Urine', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T20:22:00Z', @@ -6777,6 +6953,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Urine', + }), dict({ 'url': str, 'valueDateTime': '2022-06-16T20:22:00Z', @@ -6901,6 +7081,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Swab', + }), dict({ 'url': str, 'valueDateTime': '2022-06-20T11:58:00Z', @@ -6998,6 +7182,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Swab', + }), dict({ 'url': str, 'valueDateTime': '2022-06-20T11:58:00Z', @@ -7095,6 +7283,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Swab', + }), dict({ 'url': str, 'valueDateTime': '2022-06-20T11:58:00Z', @@ -7192,6 +7384,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Swab', + }), dict({ 'url': str, 'valueDateTime': '2022-06-20T11:58:00Z', @@ -7289,6 +7485,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Swab', + }), dict({ 'url': str, 'valueDateTime': '2022-06-20T11:58:00Z', @@ -7343,6 +7543,10 @@ 'extension': list([ dict({ 'extension': list([ + dict({ + 'url': str, + 'valueString': 'Swab', + }), dict({ 'url': str, 'valueDateTime': '2022-06-20T11:58:00Z', From 8a4842b6d61937fcc8d597eccda147387571434d Mon Sep 17 00:00:00 2001 From: Lina Roth Date: Tue, 17 Dec 2024 16:47:24 -0500 Subject: [PATCH 2/7] Add alcohol use, intake, and comment (#3043) * init * Add alcohol use, intake, and comment. * remove bad change * Update to evaluateValue * pr comments * PR comment * Fix ? to && * Upgrade fhir-converter to v18 * update snap * one last snap update * ugh * fix integration test * change to correct release * update to next release * Fix orch pin multipart * update fastapi * undo * increase timeout * revert snap --- .../ecr-viewer/src/app/api/fhirPath.yml | 4 +- .../app/services/evaluateFhirDataService.ts | 36 +++- .../src/app/tests/assets/BundlePatient.json | 165 ++++++++++++++++++ .../services/evaluateFhirDataServices.test.ts | 22 +++ containers/fhir-converter/Dockerfile | 2 +- .../tests/integration/test_orchestration.py | 2 +- poetry.lock | 2 +- 7 files changed, 228 insertions(+), 5 deletions(-) diff --git a/containers/ecr-viewer/src/app/api/fhirPath.yml b/containers/ecr-viewer/src/app/api/fhirPath.yml index 46aeb97a9..7f6034ff5 100644 --- a/containers/ecr-viewer/src/app/api/fhirPath.yml +++ b/containers/ecr-viewer/src/app/api/fhirPath.yml @@ -21,7 +21,9 @@ patientCurrentJobTitle: "Bundle.entry.resource.where(resourceType='Observation') patientTobaccoUse: "Bundle.entry.resource.where(resourceType='Observation').where(code.coding.code='72166-2').where(category.coding.code='social-history').value" patientHomelessStatus: "Bundle.entry.resource.where(resourceType='Observation').where(code.coding.code='75274-1').where(category.coding.code='social-history').value" patientPregnancyStatus: "Bundle.entry.resource.where(resourceType='Observation').where(meta.profile='http://hl7.org/fhir/us/ecr/StructureDefinition/us-ph-pregnancy-status-observation').value" -patientAlcoholUse: "Bundle.entry.resource.where(resourceType='Observation').where(code.coding.code='11331-6').where(category.coding.code='social-history').value" +patientAlcoholUse: "Bundle.entry.resource.where(resourceType='Observation').where(code.coding.where(code='11331-6' and system = 'http://loinc.org')).value" +patientAlcoholIntake: "Bundle.entry.resource.where(resourceType='Observation').where(code.coding.where(code='74013-4' and system = 'http://loinc.org')).value" +patientAlcoholComment: "Bundle.entry.resource.where(resourceType='Observation').where(code.coding.display='Alcohol Comment').value" patientSexualOrientation: "Bundle.entry.resource.where(resourceType='Observation').where(code.coding.code='76690-7').value" patientGenderIdentity: "Bundle.entry.resource.where(resourceType = 'Patient').extension.where(url='http://hl7.org/fhir/us/ecr/StructureDefinition/us-ph-genderidentity-extension').value" patientReligion: "Bundle.entry.resource.where(resourceType = 'Patient').extension.where(url='http://hl7.org/fhir/StructureDefinition/patient-religion').value" diff --git a/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts b/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts index ca60b3d61..c4ab05bce 100644 --- a/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts +++ b/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts @@ -31,6 +31,7 @@ import { DisplayDataProps } from "@/app/view-data/components/DataDisplay"; import { evaluateTravelHistoryTable } from "./socialHistoryService"; import { Path } from "fhirpath"; import { returnTableFromJson } from "../view-data/components/common"; +import { toSentenceCase } from "./formatService"; /** * Evaluates patient name from the FHIR bundle and formats it into structured data for display. @@ -197,6 +198,39 @@ export const calculatePatientAgeAtDeath = ( } }; +/** + * Evaluates alcohol use information from the FHIR bundle and formats it into structured data for display. + * @param fhirBundle - The FHIR bundle containing alcohol use data. + * @param fhirMappings - The object containing the fhir paths. + * @returns An array of evaluated and formatted alcohol use data. + */ +export const evaluateAlcoholUse = ( + fhirBundle: Bundle, + fhirMappings: PathMappings, +) => { + const alcoholUse = evaluateValue(fhirBundle, fhirMappings.patientAlcoholUse); + const alcoholIntake = evaluateValue( + fhirBundle, + fhirMappings.patientAlcoholIntake, + ); + let alcoholComment: string | undefined = evaluateValue( + fhirBundle, + fhirMappings.patientAlcoholComment, + ); + + if (alcoholComment) { + alcoholComment = toSentenceCase(alcoholComment); + } + + return [ + alcoholUse ? `Use: ${alcoholUse}` : null, + alcoholIntake ? `Intake (standard drinks/week): ${alcoholIntake}` : null, + alcoholComment ? `Comment: ${alcoholComment}` : null, + ] + .filter(Boolean) // Removes null or undefined lines + .join("\n"); // Joins the remaining lines with newlines +}; + /** * Evaluates social data from the FHIR bundle and formats it into structured data for display. * @param fhirBundle - The FHIR bundle containing social data. @@ -227,7 +261,7 @@ export const evaluateSocialData = ( }, { title: "Alcohol Use", - value: evaluateValue(fhirBundle, mappings["patientAlcoholUse"]), + value: evaluateAlcoholUse(fhirBundle, mappings), }, { title: "Sexual Orientation", diff --git a/containers/ecr-viewer/src/app/tests/assets/BundlePatient.json b/containers/ecr-viewer/src/app/tests/assets/BundlePatient.json index 1a49d2967..fc6e3753e 100644 --- a/containers/ecr-viewer/src/app/tests/assets/BundlePatient.json +++ b/containers/ecr-viewer/src/app/tests/assets/BundlePatient.json @@ -177,6 +177,171 @@ "method": "PUT", "url": "Patient/6b6b3c4c-4884-4a96-b6ab-c46406839cea" } + }, + { + "fullUrl": "urn:uuid:b91cab75-e4a0-2227-79ec-44c9c8ea795a", + "resource": { + "resourceType": "Observation", + "id": "b91cab75-e4a0-2227-79ec-44c9c8ea795a", + "meta": { + "profile": [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-observationresults" + ], + "source": "ecr" + }, + "identifier": [ + { + "system": "urn:oid:1.2.840.114350.1.13.66.2.7.7.698084.19222", + "value": "Z13909121" + } + ], + "category": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "code": "social-history" + } + ] + } + ], + "status": "final", + "code": { + "coding": [ + { + "code": "X-SDOH-19222", + "system": "urn:oid:1.2.840.114350.1.72.1.8", + "display": "Alcohol Comment" + } + ] + }, + "effectiveDateTime": "2022-11-04", + "valueString": "1-2 DRINKS 2 TO 4 TIMES A MONTH", + "subject": { + "reference": "Patient/5227f761-a6ea-461d-9523-8d9ee26eab84" + } + }, + "request": { + "method": "PUT", + "url": "Observation/b91cab75-e4a0-2227-79ec-44c9c8ea795a" + } + }, + { + "fullUrl": "urn:uuid:259df789-9da2-9b7c-bc69-a60c634860d8", + "resource": { + "resourceType": "Observation", + "id": "259df789-9da2-9b7c-bc69-a60c634860d8", + "meta": { + "profile": [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-observationresults" + ], + "source": "ecr" + }, + "identifier": [ + { + "system": "urn:oid:1.2.840.114350.1.13.66.2.7.1.1040.13", + "value": "Z13909121^54660.98^897148007" + } + ], + "category": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "code": "social-history" + } + ] + } + ], + "status": "final", + "code": { + "coding": [ + { + "code": "897148007", + "system": "http://snomed.info/sct", + "display": "Alcoholic beverage intake" + }, + { + "code": "74013-4", + "system": "http://loinc.org", + "display": "Alcoholic drinks per day" + } + ] + }, + "effectiveDateTime": "2024-01-31", + "valueQuantity": { + "value": ".29", + "unit": "/d" + }, + "subject": { + "reference": "Patient/5227f761-a6ea-461d-9523-8d9ee26eab84" + } + }, + "request": { + "method": "PUT", + "url": "Observation/259df789-9da2-9b7c-bc69-a60c634860d8" + } + }, + { + "fullUrl": "urn:uuid:b467f663-f4f5-78af-a677-05b5545d8716", + "resource": { + "resourceType": "Observation", + "id": "b467f663-f4f5-78af-a677-05b5545d8716", + "meta": { + "profile": [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-observationresults" + ], + "source": "ecr" + }, + "identifier": [ + { + "system": "urn:oid:1.2.840.114350.1.13.66.2.7.1.1040.12", + "value": "Z13909121^54660.98^897148007" + } + ], + "category": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "code": "social-history" + } + ] + } + ], + "status": "final", + "code": { + "coding": [ + { + "code": "897148007", + "system": "http://snomed.info/sct", + "display": "Alcoholic beverage intake" + }, + { + "code": "11331-6", + "system": "http://loinc.org", + "display": "History of Alcohol Use" + } + ] + }, + "effectiveDateTime": "2024-01-31", + "valueCodeableConcept": { + "coding": [ + { + "code": "219006", + "system": "http://snomed.info/sct", + "display": "Current drinker of alcohol (finding)" + } + ] + }, + "subject": { + "reference": "Patient/5227f761-a6ea-461d-9523-8d9ee26eab84" + } + }, + "request": { + "method": "PUT", + "url": "Observation/b467f663-f4f5-78af-a677-05b5545d8716" + } } ] } diff --git a/containers/ecr-viewer/src/app/tests/services/evaluateFhirDataServices.test.ts b/containers/ecr-viewer/src/app/tests/services/evaluateFhirDataServices.test.ts index 3c9dcb9ac..bb35500fe 100644 --- a/containers/ecr-viewer/src/app/tests/services/evaluateFhirDataServices.test.ts +++ b/containers/ecr-viewer/src/app/tests/services/evaluateFhirDataServices.test.ts @@ -12,6 +12,7 @@ import { evaluatePatientName, evaluateDemographicsData, evaluateEncounterCareTeamTable, + evaluateAlcoholUse, } from "@/app/services/evaluateFhirDataService"; import { Bundle, Patient } from "fhir/r4"; import BundleWithMiscNotes from "@/app/tests/assets/BundleMiscNotes.json"; @@ -516,3 +517,24 @@ describe("Evaluate Patient Name", () => { expect(actual).toEqual("ABEL CASTILLO"); }); }); + +describe("Evaluate Alcohol Use", () => { + it("should return the use, intake comment", () => { + const actual = evaluateAlcoholUse( + BundleWithPatient as unknown as Bundle, + mappings, + ); + expect(actual).toEqual( + "Use: Current drinker of alcohol (finding)\n" + + "Intake (standard drinks/week): .29/d\n" + + "Comment: 1-2 drinks 2 to 4 times a month", + ); + }); + it("should empty string because there is no use, intake, or comment", () => { + const actual = evaluateAlcoholUse( + BundlePatientMultiple as unknown as Bundle, + mappings, + ); + expect(actual).toEqual(""); + }); +}); diff --git a/containers/fhir-converter/Dockerfile b/containers/fhir-converter/Dockerfile index e234fd472..fa71046e9 100644 --- a/containers/fhir-converter/Dockerfile +++ b/containers/fhir-converter/Dockerfile @@ -1,7 +1,7 @@ FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build # Download FHIR-Converter -RUN git clone https://github.com/skylight-hq/FHIR-Converter.git --branch v7.0-skylight-17 --single-branch /build/FHIR-Converter +RUN git clone https://github.com/skylight-hq/FHIR-Converter.git --branch v7.0-skylight-18 --single-branch /build/FHIR-Converter WORKDIR /build/FHIR-Converter diff --git a/containers/orchestration/tests/integration/test_orchestration.py b/containers/orchestration/tests/integration/test_orchestration.py index 735e88565..1dccb4821 100644 --- a/containers/orchestration/tests/integration/test_orchestration.py +++ b/containers/orchestration/tests/integration/test_orchestration.py @@ -148,7 +148,7 @@ def test_failed_save_to_ecr_viewer(setup, clean_up_db): } files = {"upload_file": ("file.zip", file)} orchestration_response = httpx.post( - PROCESS_ZIP_ENDPOINT, data=form_data, files=files, timeout=60 + PROCESS_ZIP_ENDPOINT, data=form_data, files=files, timeout=120 ) assert orchestration_response.status_code == 500 diff --git a/poetry.lock b/poetry.lock index 2e54c2999..b72447e36 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "antlr4-python3-runtime" From 750289c30335860333bec0f2ff42245dbc87aba8 Mon Sep 17 00:00:00 2001 From: Mary McGrath Date: Tue, 17 Dec 2024 17:08:02 -0500 Subject: [PATCH 3/7] Create CODEOWNERS (#3069) * Create CODEOWNERS * Update .github/CODEOWNERS * Add team 4 engs * fix: robert --- .github/CODEOWNERS | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..3e8830f14 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,4 @@ +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# they will be requested for review when someone opens a pull request. +* @akasper @angelathe @austin-hall-skylight @BobanL @gordonfarrell @JNygaard-Skylight @lina-roth @mcmcgrath13 @robertmitchellv From 29a88f2c42994cfed1cdbc063c34e3ef97d2dc25 Mon Sep 17 00:00:00 2001 From: Mary McGrath Date: Wed, 18 Dec 2024 12:18:03 -0500 Subject: [PATCH 4/7] fix: don't display "Lab Interpretation" component (#3071) * fix: don't display "Lab Interpretation" component * fix: reset ecr formatting * fix: pr feedback --- .../src/app/services/labsService.tsx | 6 +- .../src/app/tests/assets/BundleLab.json | 74 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/containers/ecr-viewer/src/app/services/labsService.tsx b/containers/ecr-viewer/src/app/services/labsService.tsx index bb2e085fe..8fac36177 100644 --- a/containers/ecr-viewer/src/app/services/labsService.tsx +++ b/containers/ecr-viewer/src/app/services/labsService.tsx @@ -332,7 +332,11 @@ export function evaluateObservationTable( ).filter( (observation) => !observation.component && - observation.code?.coding.some((c: Coding) => c?.display), + // Make sure there is a component name, but it isn't "Lab Interpretation" as that's handled + // via the tab on the result's name + observation.code?.coding.some( + (c: Coding) => c?.display && c?.display !== "Lab Interpretation", + ), ); let obsTable; diff --git a/containers/ecr-viewer/src/app/tests/assets/BundleLab.json b/containers/ecr-viewer/src/app/tests/assets/BundleLab.json index 5136dc422..7be4f6862 100644 --- a/containers/ecr-viewer/src/app/tests/assets/BundleLab.json +++ b/containers/ecr-viewer/src/app/tests/assets/BundleLab.json @@ -83,6 +83,10 @@ { "display": "Organism ID", "reference": "Observation/2a4f8fb5-f957-3c30-1353-a30aa441a12b" + }, + { + "reference": "Observation/e9c8e4b3-3fec-4f29-a5c5-e45f96680a4c", + "display": "Lab Interpretation" } ] } @@ -265,6 +269,9 @@ { "reference": "Observation/1c0f3367-0588-c90e-fed0-0d8c15c5ac1b" }, + { + "reference": "Observation/e9c8e4b3-3fec-4f29-a5c5-e45f96680a4c" + }, { "reference": "Observation/ab1ecbd4-6de0-4f78-cea6-a880a15e88bb" } @@ -396,6 +403,73 @@ "url": "DiagnosticReport/b0f590a6-4bf5-7add-9716-2bd3ba6defb2" } }, + { + "fullUrl": "urn:uuid:e9c8e4b3-3fec-4f29-a5c5-e45f96680a4c", + "resource": { + "resourceType": "Observation", + "id": "e9c8e4b3-3fec-4f29-a5c5-e45f96680a4c", + "meta": { + "profile": [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-observationresults" + ], + "source": "ecr" + }, + "identifier": [ + { + "system": "urn:oid:1.2.840.114350.1.13.202.3.7.2.798268", + "value": "476858944" + } + ], + "category": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "code": "laboratory" + } + ] + } + ], + "status": "final", + "code": { + "coding": [ + { + "code": "56850-1", + "system": "http://loinc.org", + "display": "Lab Interpretation" + } + ] + }, + "effectiveDateTime": "2022-09-29T21:13:00Z", + "valueString": "Abnormal", + "extension": [ + { + "url": "http://hl7.org/fhir/R4/specimen.html", + "extension": [ + { + "url": "specimen source", + "valueString": "Stool" + }, + { + "url": "specimen collection time", + "valueDateTime": "2022-09-28T20:51:00Z" + }, + { + "url": "specimen receive time", + "valueDateTime": "2022-09-28T20:51:36Z" + } + ] + } + ], + "subject": { + "reference": "Patient/1586f68b-1a17-4188-ac0b-82c3cbdbfbda" + } + }, + "request": { + "method": "PUT", + "url": "Observation/e9c8e4b3-3fec-4f29-a5c5-e45f96680a4c" + } + }, { "fullUrl": "urn:uuid:1c0f3367-0588-c90e-fed0-0d8c15c5ac1b", "resource": { From 7b7e8be7a4e80d6ddd69005e91425dd9a1e8e6cd Mon Sep 17 00:00:00 2001 From: Amy Kintner Date: Wed, 18 Dec 2024 09:43:54 -0800 Subject: [PATCH 5/7] adds country to patient info for ecr view (#3045) * adds country to patient info for ecr view * update snapshot * updates snapshots for test --- .../ecr-viewer/src/app/api/fhirPath.yml | 1 + .../app/services/evaluateFhirDataService.ts | 4 ++++ .../tests/components/Demographics.test.tsx | 4 ++++ .../AccordionContent.test.tsx.snap | 19 +++++++++++++++++++ .../__snapshots__/Demographics.test.tsx.snap | 19 +++++++++++++++++++ 5 files changed, 47 insertions(+) diff --git a/containers/ecr-viewer/src/app/api/fhirPath.yml b/containers/ecr-viewer/src/app/api/fhirPath.yml index 7f6034ff5..6361e79e1 100644 --- a/containers/ecr-viewer/src/app/api/fhirPath.yml +++ b/containers/ecr-viewer/src/app/api/fhirPath.yml @@ -2,6 +2,7 @@ patientNameList: "Bundle.entry.resource.where(resourceType = 'Patient').name" patientAddressList: "Bundle.entry.resource.where(resourceType = 'Patient').address" patientTelecom: "Bundle.entry.resource.where(resourceType = 'Patient').telecom" patientCounty: "Bundle.entry.resource.where(resourceType = 'Patient').address.first().county" +patientCountry: "Bundle.entry.resource.where(resourceType = 'Patient').address.first().country" patientIds: "Bundle.entry.resource.where(resourceType = 'Patient').identifier.where(system != 'urn:ietf:rfc:3986').value.join('\n')" patientDOB: "Bundle.entry.resource.where(resourceType = 'Patient').birthDate" diff --git a/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts b/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts index c4ab05bce..646e4fe61 100644 --- a/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts +++ b/containers/ecr-viewer/src/app/services/evaluateFhirDataService.ts @@ -347,6 +347,10 @@ export const evaluateDemographicsData = ( title: "County", value: evaluate(fhirBundle, mappings.patientCounty)[0], }, + { + title: "Country", + value: evaluate(fhirBundle, mappings.patientCountry)[0], + }, { title: "Contact", value: formatContactPoint(evaluate(fhirBundle, mappings.patientTelecom)), diff --git a/containers/ecr-viewer/src/app/tests/components/Demographics.test.tsx b/containers/ecr-viewer/src/app/tests/components/Demographics.test.tsx index 020f6465c..d7a39cb75 100644 --- a/containers/ecr-viewer/src/app/tests/components/Demographics.test.tsx +++ b/containers/ecr-viewer/src/app/tests/components/Demographics.test.tsx @@ -35,6 +35,10 @@ describe("Demographics", () => { title: "County", value: "test", }, + { + title: "Country", + value: "USA", + }, { title: "Contact", value: "test contact" }, { title: "Emergency Contact", diff --git a/containers/ecr-viewer/src/app/tests/components/__snapshots__/AccordionContent.test.tsx.snap b/containers/ecr-viewer/src/app/tests/components/__snapshots__/AccordionContent.test.tsx.snap index 2a54fe172..20fddbc24 100644 --- a/containers/ecr-viewer/src/app/tests/components/__snapshots__/AccordionContent.test.tsx.snap +++ b/containers/ecr-viewer/src/app/tests/components/__snapshots__/AccordionContent.test.tsx.snap @@ -439,6 +439,25 @@ exports[`Snapshot test for Accordion Content Given no data, info message for emp class="section__line_gray" /> +
+
+
+ Country +
+
+ No data +
+
+
+
+
+
+
+ Country +
+
+ USA +
+
+
+
Date: Thu, 19 Dec 2024 10:15:06 -0500 Subject: [PATCH 6/7] set ecr viewer path permanently (#3053) * permanently set base path in ecr-viewer * Rename middleware to be a .ts file * replace a tags with Next's Link tag * remove title from Header link * retype base_path to be a string * fix metrics test * update header snapshot test * address middleware tests * add back button to retrieval failed * remove unused rewrite in next config * update matcher * Prevent access to /api/fhir-data * [pre-commit.ci] auto fixes from pre-commit hooks * fix url in readme * remove accident commited next-runtime-env * [pre-commit.ci] auto fixes from pre-commit hooks * Filter by conditions in eCR Library (frontend) (#2981) * First pass, filter conditions functionality * add select/deselect all functionality * update some styling, maintain checkbox state when toggling filter button * styling updates, wip * checkbox color, add icon, add uswds sprite.svg to assets * adjust padding to fix checkbox focus ring cut off * fix icon not displaying by adding static file route * fix unintentional scrolling bug * update filter row top border * wip, add comments, decompose conditions filter to separate const * fix scrolling bug by adding position-relative * add snapshot and unit tests * add JSDocs * remove css classes and use utilities instead * update snapshot test * update select all/deselect all functionality s.t. default is all conditions checked, update tests * update so that filters reset if clicking off filter before clicking the Apply button, add tests * update basepath so it works in prod * update tests * update styles in diff button states, update icon size, make capitalization consistent * Remove log Co-authored-by: Mary McGrath * use as form/fieldset, update sync state bug, update tests * remove manual checkboxing for select all, lets react handle the render * rework state management, update tests * code review changes, minor * query should persist over a reload * update backend so default (all conditions) would leave out condition param from URL query, add/update tests * use import for icon * Update base_path env var name Co-authored-by: Boban * update snapshot test * re-use resetFilterConditions * one more nit * update ecr library height to accommodate fiter bar * update env var name for base path --------- Co-authored-by: Mary McGrath Co-authored-by: Boban * fix base path for filters * [pre-commit.ci] auto fixes from pre-commit hooks * update filter test * Update middleware.ts --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Angela The Co-authored-by: Mary McGrath --- containers/ecr-viewer/README.md | 14 ++++--- containers/ecr-viewer/environment.d.ts | 1 + containers/ecr-viewer/next.config.js | 10 +---- .../src/app/components/EcrTableClient.tsx | 8 ++-- .../ecr-viewer/src/app/components/Filters.tsx | 5 +-- .../src/app/tests/components/Filters.test.tsx | 8 +++- .../src/app/tests/view-data.test.tsx | 8 +++- .../ecr-viewer/src/app/view-data/page.tsx | 2 +- .../src/app/view-data/retrieval-failed.tsx | 2 + ...{middleware.test.js => middleware.test.ts} | 38 +++++++++++++------ containers/ecr-viewer/src/middleware.ts | 24 +++++++++++- 11 files changed, 78 insertions(+), 42 deletions(-) rename containers/ecr-viewer/src/{middleware.test.js => middleware.test.ts} (65%) diff --git a/containers/ecr-viewer/README.md b/containers/ecr-viewer/README.md index fb5d24f66..3f85f4b59 100644 --- a/containers/ecr-viewer/README.md +++ b/containers/ecr-viewer/README.md @@ -37,9 +37,9 @@ To build the Docker image for the eCR Viewer from source instead of downloading 3. Navigate to `/phdi/containers/ecr-viewer/`. 4. Run `docker build -t ecr-viewer .`. -## Non Integrated Viewer +## Non Integrated Viewer -To enable the Non Integrated Viewer homepage, set the environment variable `NEXT_PUBLIC_NON_INTEGRATED_VIEWER` equal to `true`. This will enable the Non Integrated viewer homepage at `localhost:3000`. +To enable the Non Integrated Viewer homepage, set the environment variable `NEXT_PUBLIC_NON_INTEGRATED_VIEWER` equal to `true`. This will enable the Non Integrated viewer homepage at `localhost:3000/ecr-viewer`. For local development, if `NEXT_PUBLIC_NON_INTEGRATED_VIEWER` is not set equal to `true` on `.env.local`, convert-seed-data will not seed the metadata. @@ -50,11 +50,12 @@ If you have problems connecting to your database, use this command to see what o If building the Docker Image doesn't work as expected, try to first run the eCR Viewer locally using the steps below. -If you consistently encounter the error message `"ecr_viewer_db" does not exist` when attempting to run the app, there could be conflicting databases running on port 5432 as part of other background processes. Try pruning any dangling Docker images and containers (`docker image prune` and `docker container prune`). If issues persist, try logging into `psql` on the command line to see what databases are running there. +If you consistently encounter the error message `"ecr_viewer_db" does not exist` when attempting to run the app, there could be conflicting databases running on port 5432 as part of other background processes. Try pruning any dangling Docker images and containers (`docker image prune` and `docker container prune`). If issues persist, try logging into `psql` on the command line to see what databases are running there. ## Development ### Run eCR Viewer Locally + To run the eCR Viewer locally: 1. Ensure that Git, Docker, and Node (version 18.x or higher) are installed. @@ -63,7 +64,7 @@ To run the eCR Viewer locally: 4. Install all of the Node dependencies for the eCR Viewer with `npm install`. 5. Setup your `.env.local` by running `npm run setup-local-env`. 6. Create seed data with `npm run convert-seed-data` - this will take ~10 minutes. Note that this process will fail immediately if the Docker daemon isn't running. -7. Run the eCR Viewer on `localhost:3000` with `npm run local-dev`. +7. Run the eCR Viewer on `localhost:3000/ecr-viewer` with `npm run local-dev`. ### Windows Setup @@ -82,8 +83,8 @@ Sample eICRs are included in `containers/ecr-viewer/seed-scripts/baseECR/`. If y Additional commands can be found in [`package.json`](package.json) and are also made available in [`Makefile`](Makefile). ## API Documentation -Can be found in [api-documentation.md](api-documentation.md). +Can be found in [api-documentation.md](api-documentation.md). # Architecture Diagram @@ -105,7 +106,7 @@ flowchart LR end end - + subgraph service[REST API Service] direction TB subgraph mr["fab:fa-docker container"] @@ -135,6 +136,7 @@ ecr ===> mr ===> post-ecr ``` #### Application API + ```mermaid graph TD A[ecr-viewer] diff --git a/containers/ecr-viewer/environment.d.ts b/containers/ecr-viewer/environment.d.ts index 8c7807056..35eb030e3 100644 --- a/containers/ecr-viewer/environment.d.ts +++ b/containers/ecr-viewer/environment.d.ts @@ -8,6 +8,7 @@ namespace NodeJS { AWS_SECRET_ACCESS_KEY: string; AZURE_CONTAINER_NAME: string; AZURE_STORAGE_CONNECTION_STRING: string; + BASE_PATH: string; DATABASE_TYPE: string; DATABASE_URL: string; ECR_BUCKET_NAME: string; diff --git a/containers/ecr-viewer/next.config.js b/containers/ecr-viewer/next.config.js index 3dd060dd7..e374dd1bc 100644 --- a/containers/ecr-viewer/next.config.js +++ b/containers/ecr-viewer/next.config.js @@ -12,16 +12,8 @@ const nextConfig = { instrumentationHook: true, // this needs to be here for opentelemetry }, transpilePackages: ["yaml"], - async rewrites() { - return [ - { - source: "/ecr-viewer/:slug*", - destination: "/:slug*", - }, - ]; - }, output: "standalone", - basePath: process.env.NODE_ENV === "production" ? basePath : "", + basePath: basePath, env: { BASE_PATH: basePath, }, diff --git a/containers/ecr-viewer/src/app/components/EcrTableClient.tsx b/containers/ecr-viewer/src/app/components/EcrTableClient.tsx index 5e4d6df58..d19d531fb 100644 --- a/containers/ecr-viewer/src/app/components/EcrTableClient.tsx +++ b/containers/ecr-viewer/src/app/components/EcrTableClient.tsx @@ -7,9 +7,7 @@ import { toSentenceCase } from "@/app/services/formatService"; import { usePathname, useSearchParams, useRouter } from "next/navigation"; import { range } from "../view-data/utils/utils"; import classNames from "classnames"; - -const basePath = - process.env.NODE_ENV === "production" ? process.env.BASE_PATH : ""; +import Link from "next/link"; type EcrTableClientProps = { data: EcrDisplay[]; @@ -304,9 +302,9 @@ const DataRow = ({ item }: { item: EcrDisplay }) => { return ( - + {patient_first_name} {patient_last_name} - +
{"DOB: " + item.patient_date_of_birth || ""}
diff --git a/containers/ecr-viewer/src/app/components/Filters.tsx b/containers/ecr-viewer/src/app/components/Filters.tsx index 91213e47a..1a4d2a021 100644 --- a/containers/ecr-viewer/src/app/components/Filters.tsx +++ b/containers/ecr-viewer/src/app/components/Filters.tsx @@ -4,9 +4,6 @@ import React, { useCallback, useEffect, useState } from "react"; import { Button, Icon } from "@trussworks/react-uswds"; import { useRouter, usePathname, useSearchParams } from "next/navigation"; -const basePath = - process.env.NODE_ENV === "production" ? process.env.BASE_PATH : ""; - /** * Functional component that renders Filters section in eCR Library. * Includes Filter component for reportable conditions. @@ -45,7 +42,7 @@ const FilterReportableConditions = () => { useEffect(() => { const fetchConditions = async () => { try { - const response = await fetch(`${basePath}/api/conditions`); + const response = await fetch(`${process.env.BASE_PATH}/api/conditions`); if (!response.ok) { throw new Error("Failed to fetch conditions"); } diff --git a/containers/ecr-viewer/src/app/tests/components/Filters.test.tsx b/containers/ecr-viewer/src/app/tests/components/Filters.test.tsx index 2b08d5fd8..c15a06156 100644 --- a/containers/ecr-viewer/src/app/tests/components/Filters.test.tsx +++ b/containers/ecr-viewer/src/app/tests/components/Filters.test.tsx @@ -27,7 +27,9 @@ describe("Filters Component", () => { const { container } = render(); await waitFor(() => { - expect(global.fetch).toHaveBeenCalledWith("/api/conditions"); + expect(global.fetch).toHaveBeenCalledWith( + `${process.env.BASE_PATH}/api/conditions`, + ); }); const toggleFilterButton = screen.getByRole("button", { name: /Filter by reportable condition/i, @@ -69,7 +71,9 @@ describe("Filters Component", () => { // Should have called conditions endpoint await waitFor(() => { - expect(global.fetch).toHaveBeenCalledWith("/api/conditions"); + expect(global.fetch).toHaveBeenCalledWith( + `${process.env.BASE_PATH}/api/conditions`, + ); }); fireEvent.click(toggleButton); diff --git a/containers/ecr-viewer/src/app/tests/view-data.test.tsx b/containers/ecr-viewer/src/app/tests/view-data.test.tsx index a295bfb29..167a86de1 100644 --- a/containers/ecr-viewer/src/app/tests/view-data.test.tsx +++ b/containers/ecr-viewer/src/app/tests/view-data.test.tsx @@ -23,6 +23,12 @@ function mockFetch(data: any, status?: number, statusText?: string) { } describe("ECRViewerPage", () => { + beforeAll(() => { + process.env.BASE_PATH = "ecr-viewer"; + }); + afterAll(() => { + delete process.env.BASE_PATH; + }); it("calls metrics on beforeunload", () => { window.fetch = mockFetch({}); @@ -37,7 +43,7 @@ describe("ECRViewerPage", () => { // Verify that the metrics function was called - expect(metricsMock).toHaveBeenCalledWith("", { + expect(metricsMock).toHaveBeenCalledWith("ecr-viewer", { fhirId: "", startTime: expect.any(Number), endTime: expect.any(Number), diff --git a/containers/ecr-viewer/src/app/view-data/page.tsx b/containers/ecr-viewer/src/app/view-data/page.tsx index 5a0bf683f..208caa05d 100644 --- a/containers/ecr-viewer/src/app/view-data/page.tsx +++ b/containers/ecr-viewer/src/app/view-data/page.tsx @@ -48,7 +48,7 @@ const ECRViewerPage: React.FC = () => { useEffect(() => { const startTime = performance.now(); window.addEventListener("beforeunload", function (_e) { - metrics("", { + metrics(process.env.BASE_PATH, { startTime: startTime, endTime: performance.now(), fhirId: `${fhirId}`, diff --git a/containers/ecr-viewer/src/app/view-data/retrieval-failed.tsx b/containers/ecr-viewer/src/app/view-data/retrieval-failed.tsx index 68975c572..65002aa2c 100644 --- a/containers/ecr-viewer/src/app/view-data/retrieval-failed.tsx +++ b/containers/ecr-viewer/src/app/view-data/retrieval-failed.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Icon } from "@trussworks/react-uswds"; import Header from "../Header"; +import { BackButton } from "./components/BackButton"; /** * @returns The ecr retrieval error page JSX component. @@ -31,6 +32,7 @@ const RetrievalFailed = () => (
to troubleshoot the issue with the DIBBs team.
+
diff --git a/containers/ecr-viewer/src/middleware.test.js b/containers/ecr-viewer/src/middleware.test.ts similarity index 65% rename from containers/ecr-viewer/src/middleware.test.js rename to containers/ecr-viewer/src/middleware.test.ts index 8f86684f0..831f3ac71 100644 --- a/containers/ecr-viewer/src/middleware.test.js +++ b/containers/ecr-viewer/src/middleware.test.ts @@ -20,13 +20,21 @@ describe("Middleware", () => { beforeEach(() => { process.env.NEXTAUTH_SECRET = "test-secret"; process.env.APP_ENV = "middleware"; + process.env.BASE_PATH = "ecr-viewer"; jest.resetAllMocks(); // Reset mocks before each test }); + afterEach(() => { + delete process.env.NEXTAUTH_SECRET; + delete process.env.APP_ENV; + delete process.env.BASE_PATH; + }); it("should authorize the request if oauthToken is present", async () => { getToken.mockImplementation(() => Promise.resolve({ token: "fake-token" })); - const req = new NextRequest("https://www.example.com/api/protected"); + const req = new NextRequest( + "https://www.example.com/ecr-viewer/api/protected", + ); const resp = await middleware(req); @@ -39,7 +47,7 @@ describe("Middleware", () => { it("should strip the auth query param and set the token", async () => { const req = new NextRequest( - "https://www.example.com/api?id=1234&auth=abcd", + "https://www.example.com/ecr-viewer/api?id=1234&auth=abcd", ); const resp = await middleware(req); @@ -50,24 +58,28 @@ describe("Middleware", () => { httpOnly: true, }); expect(resp.headers.get("location")).toBe( - "https://www.example.com/api?id=1234", + "https://www.example.com/ecr-viewer/api?id=1234", ); }); it("should not authorize the api endpoints without auth", async () => { - const req = new NextRequest("https://www.example.com/api/fhir-data/"); + const req = new NextRequest( + "https://www.example.com/ecr-viewer/api/fhir-data/", + ); const resp = await middleware(req); - expect(resp.headers.get("location")).toBe( - "https://www.example.com/error/auth", + expect(resp.headers.get("x-middleware-rewrite")).toBe( + "https://www.example.com/ecr-viewer/error/auth", ); - expect(resp.status).toBe(307); + expect(resp.status).toBe(200); }); it("should authorize the api endpoints with auth", async () => { process.env.NBS_PUB_KEY = "FOOBAR"; - const req = new NextRequest("https://www.example.com/api/fhir-data/"); + const req = new NextRequest( + "https://www.example.com/ecr-viewer/api/fhir-data/", + ); req.cookies.set("auth-token", "foobar"); const resp = await middleware(req); @@ -78,11 +90,13 @@ describe("Middleware", () => { }); it("should not authorize non api endpoints ", async () => { - const req = new NextRequest("https://www.example.com/view-data?id=1234"); + const req = new NextRequest( + "https://www.example.com/ecr-viewer/view-data?id=1234", + ); const resp = await middleware(req); - expect(resp.headers.get("location")).toBe( - "https://www.example.com/error/auth", + expect(resp.headers.get("x-middleware-rewrite")).toBe( + "https://www.example.com/ecr-viewer/error/auth", ); - expect(resp.status).toBe(307); + expect(resp.status).toBe(200); }); }); diff --git a/containers/ecr-viewer/src/middleware.ts b/containers/ecr-viewer/src/middleware.ts index a82daeaf1..255022ffa 100644 --- a/containers/ecr-viewer/src/middleware.ts +++ b/containers/ecr-viewer/src/middleware.ts @@ -26,7 +26,9 @@ export async function middleware(req: NextRequest): Promise { if (nbsAuth) { return nbsAuth; } else { - return NextResponse.redirect(new URL("/error/auth", req.url)); + return NextResponse.rewrite( + new URL(`${process.env.BASE_PATH}/error/auth`, req.nextUrl.origin), + ); } } } else { @@ -35,7 +37,25 @@ export async function middleware(req: NextRequest): Promise { } export const config = { - matcher: ["/api/fhir-data", "/api/metrics", "/view-data", "/"], + matcher: [ + /* + * Run middleware on all request paths except these: + * - Api routes + * - _next/static (static files) + * - _next/image (image optimization files) + * - images (static files in public/images/ directory) + */ + "/((?!api|_next/static|_next/image|public|img|uswds|images).*)", + /** + * Fix issue where the pattern above was causing middleware + * to not run on the homepage: + */ + "/", + /** + * Run middleware on getting fhir-data + */ + "/api/fhir-data", + ], }; /** From fe696083ff12932fba35758e968d1b0e763a9c00 Mon Sep 17 00:00:00 2001 From: Gordon Farrell <93161643+gordonfarrell@users.noreply.github.com> Date: Thu, 19 Dec 2024 08:18:55 -0700 Subject: [PATCH 7/7] Add test interpretation and interp code to extended schema saving (#3073) * add interpretation and interpretation code to extended sqlserver saving * remove old comments --- .../api/save-fhir-data/save-fhir-data-service.ts | 14 +++++++++++--- .../ecr-viewer/src/app/api/save-fhir-data/types.ts | 4 +++- .../app/default_schemas/extended.json | 6 +++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/containers/ecr-viewer/src/app/api/save-fhir-data/save-fhir-data-service.ts b/containers/ecr-viewer/src/app/api/save-fhir-data/save-fhir-data-service.ts index 0694958d5..dccfb61b7 100644 --- a/containers/ecr-viewer/src/app/api/save-fhir-data/save-fhir-data-service.ts +++ b/containers/ecr-viewer/src/app/api/save-fhir-data/save-fhir-data-service.ts @@ -359,12 +359,20 @@ export const saveMetadataToSqlServer = async ( sql.VarChar(50), lab.test_result_code_system, ) - .input("test_result_interpretation", sql.VarChar(255), null) // Not implemented - .input("test_result_interpretation_code", sql.VarChar(50), null) // Not implemented + .input( + "test_result_interpretation", + sql.VarChar(255), + lab.test_result_interpretation, + ) + .input( + "test_result_interpretation_code", + sql.VarChar(50), + lab.test_result_interpretation_code, + ) .input( "test_result_interpretation_system", sql.VarChar(255), - lab.test_result_interp_system, + lab.test_result_interpretation_system, ) .input( "test_result_ref_range_low_value", diff --git a/containers/ecr-viewer/src/app/api/save-fhir-data/types.ts b/containers/ecr-viewer/src/app/api/save-fhir-data/types.ts index f363ddb49..fa4aafc5b 100644 --- a/containers/ecr-viewer/src/app/api/save-fhir-data/types.ts +++ b/containers/ecr-viewer/src/app/api/save-fhir-data/types.ts @@ -9,7 +9,9 @@ interface Lab { test_result_code: string | undefined; test_result_code_display: string | undefined; test_result_code_system: string | undefined; - test_result_interp_system: string | undefined; + test_result_interpretation: string | undefined; + test_result_interpretation_code: string | undefined; + test_result_interpretation_system: string | undefined; test_result_ref_range_low: string | undefined; test_result_ref_range_low_units: string | undefined; test_result_ref_range_high: string | undefined; diff --git a/containers/message-parser/app/default_schemas/extended.json b/containers/message-parser/app/default_schemas/extended.json index 2619f13a1..d209a4afb 100644 --- a/containers/message-parser/app/default_schemas/extended.json +++ b/containers/message-parser/app/default_schemas/extended.json @@ -194,17 +194,17 @@ "data_type": "string", "nullable": true }, - "test_result_interp": { + "test_result_interpretation": { "fhir_path": "Observation.interpretation.coding.display", "data_type": "string", "nullable": true }, - "test_result_interp_code": { + "test_result_interpretation_code": { "fhir_path": "Observation.interpretation.coding.code", "data_type": "string", "nullable": true }, - "test_result_interp_system": { + "test_result_interpretation_system": { "fhir_path": "Observation.interpretation.coding.system", "data_type": "string", "nullable": true